为啥并行读取数组会导致内存泄漏?
Posted
技术标签:
【中文标题】为啥并行读取数组会导致内存泄漏?【英文标题】:why does reading an array in parallel cause memory leaks?为什么并行读取数组会导致内存泄漏? 【发布时间】:2019-09-30 02:22:09 【问题描述】:我正在使用 openmp 并行读取一个数组。下面是一个最小的可重现示例:
#include <cstdint>
#include <cstdlib>
#include <immintrin.h>
#include <iostream>
#include <memory>
#include <omp.h>
int main(int argc, char* argv[])
// align to cache line, which is 512 bits or 64 bytes
size_t actualSize = 2048;
uint8_t* array = static_cast<uint8_t *>(aligned_alloc(64, actualSize));
for(size_t i = 0; i < actualSize; i++)
// initialize values
array[i] = rand() % 256;
__m256i sum_v = _mm256_setzero_si256 ();
#pragma omp parallel for
for (size_t i = 0; i < actualSize; i+=32)
__m256i v1 = _mm256_load_si256((const __m256i *) array+i);
// i understand that there is a race condition here, but I'm just
// concerned with the memory leaks
sum_v = _mm256_add_epi8 (sum_v, v1);
// just to keep compiler from optimizing out sum_v
uint8_t result = _mm256_extract_epi8 (sum_v, 0);
std::cout << "result: " << result << std::endl;
free(array);
return 0;
这是在我的计算机上测量内存带宽的尝试,我最终会为不同的实际大小计时。
我用g++ -Wall -g -std=c++1y -march=native -mtune=native -fopenmp -O3 -g minimal-memleaks.cpp
编译这个。当我使用valgrind ./a.out
运行这个程序时,我得到一个内存泄漏,其中一部分复制到下面
==7688== Thread 8:
==7688== Invalid read of size 32
==7688== at 0x108D30: _mm256_add_epi8 (avx2intrin.h:107)
==7688== by 0x108D30: main._omp_fn.0 (minimal-memleaks.cpp:25)
==7688== by 0x51DB95D: ??? (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
==7688== by 0x5FA66DA: start_thread (pthread_create.c:463)
==7688== by 0x551588E: clone (clone.S:95)
==7688== Address 0x61e1980 is 29,280 bytes inside an unallocated block of size 4,077,760 in arena "client"
此处提供完整输出:https://pastebin.com/qr0W9FGD
我似乎不明白为什么。起初我认为循环超过了我分配的 2048 字节,但我的数学表明它不应该。我读取了 32 个块,当循环应该停止时,将 32 添加到 i 最终将等于 2048。我还认为主线程可能在子线程之前关闭,但我的研究表明,在#pragma omp parallel for
循环创建的线程关闭之前,主线程不会关闭。这是不正确的吗?
感谢您提供的任何帮助。
【问题讨论】:
内存泄漏是(大致)分配了额外的内存,您不再需要并且应该取消分配。此错误表示您正在阅读不应该阅读的内存。 指针算法:(const __m256i *) array+i
不是您期望的位置
简而言之,您在array+i
周围缺少一组括号,在添加之前评估演员表。否则你的想法很好。
@Qubit 您对操作顺序的评论是我所缺少的。谢谢!
【参考方案1】:
这不是内存泄漏。您正在读取和/或损坏内存。
for (size_t i = 0; i < actualSize; i+=32)
__m256i v1 = _mm256_load_si256((const __m256i *) array+i);
你跑到数组的末端了,这里。 actualSize
是您分配的数组的大小以字节为单位。
__m256i
是一种 32 字节长的数据类型。
(const __m256i *) array
这会将指针转换为指向 32 字节对象的指针。
指针加法在 C++ 中的工作方式是,向指针添加 1 会使指针前进到下一个对象,所以
array+1
是 next 32 字节对象所在的位置。 array
之后的 32 个字节。
因此,如果您计算出您的 for
循环最终读取了哪个地址,那么很明显它正在从您的数组末端跑到永远不会到达的地方,因此 valgrind 对您咆哮。
您的for
循环应该是:
for (size_t i = 0; i < actualSize/32; ++i)
__m256i v1 = _mm256_load_si256((const __m256i *) array+i);
【讨论】:
执行以下操作不是也有效吗?for (size_t i = 0; i < actualSize; i+=32) __m256i v1 = _mm256_load_si256((const __m256i *) (array+i));
这样,数组在被强制转换之前增加了i
字节?
是的,编译器很可能生成相同的代码。以上是关于为啥并行读取数组会导致内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Netty ByteBuf.readBytes 会导致内存泄漏?
为啥这个 INotifyCollectionChanged 会导致内存泄漏?