问题描述
首先,我要声明我无法使用示例程序复制该行为。这有点困难,因为我不确定是什么导致了问题。
我提供了一些代码来给出我想要描述的内容的某种心理地图。本质上,如标题所述,我们看到线程对大小为 >= 1KB 的 malloc 的第一次调用需要很长时间。
#include <iostream>
#include <thread>
#include <vector>
#include <ctime>
#include <ratio>
#include <chrono>
using namespace std::chrono;
class myClass {
public:
myClass();
void someFunction();
};
myClass::myClass() {;}
void myClass::someFunction() {
high_resolution_clock::time_point t1 = high_resolution_clock::Now();
int *arr1 = (int*)malloc(sizeof(int) * 1000);
high_resolution_clock::time_point t2 = high_resolution_clock::Now();
int *arr2 = (int*)malloc(sizeof(int) * 1000);
high_resolution_clock::time_point t3 = high_resolution_clock::Now();
duration<double,std::milli> time_span1 = t2 - t1;
duration<double,std::milli> time_span2 = t3 - t2;
std::cout << "first alloc took" << time_span1.count() << "milliseconds" << std::endl;
std::cout << "second alloc took" << time_span2.count() << "milliseconds" << std::endl;
}
int main() {
myClass obj1;
std::vector<std::thread> ThreadManager(1);
ThreadManager[0] = std::thread(&myClass::someFunction,obj1);
ThreadManager[0].join();
}
同样,此示例中不存在此问题。此示例显示启动线程中的第一个 malloc 花费的时间比第二个长。但是,在我的机器上,第一个 malloc 的执行需要大约 0.05 毫秒,这对我来说并不重要。第二次调用的“加速”很容易归因于 ILP 之类的东西。
在我正在做的项目中,第一个 malloc 的执行时间要差很多(5-10ms)。这仅在启动线程后第一次调用 malloc 时发生,前提是请求的内存量不可忽略 (>= ~1KB)。我注意到仅启动一个线程时会出现此问题(如示例代码中所示),因此它似乎不是同步问题。该问题可能与碎片有关,但如果我在启动线程之前请求相同数量的内存,我们看不到性能问题。此外,项目中的大多数分配都是通过一次请求大块的分配器完成的,我认为这应该会降低出现碎片问题的可能性。此外,我已经对主程序的多个输入进行了测试,出现问题的输入集是确定性的——这对我来说意味着它与运行时的复杂性无关。我应该提到,我正在处理的项目大小适中(成千上万行),并且可调用 std::thread 属于一个相对较大的类。
基本上,我不知道是什么导致了这个问题,我想知道是否有人以前见过这样的事情——如果是的话,他们是如何解决的:)
编辑:
经过进一步调查,性能bug至少与同步间接相关。 malloc
使用多个领域来处理多个同步调用。这些竞技场的数量可以通过调用 mallopt
来改变。通过mallopt(M_ARENA_MAX,1)
将最大竞技场数量改为一个后,第一次调用malloc
的性能已经恢复正常。也就是说,由于应用程序是多线程的,我想利用更多的领域,当我将最大值更改为 2 时,开销返回(第一次调用 malloc
时为 5-10 毫秒)。我想知道增加竞技场的数量会导致这样的开销。
解决方法
这可能是也可能不是同步问题。 malloc 的语言实现可能正在管理每个线程或每个处理器的空闲列表,或者可能有一个可能需要同步的内存池。因此,不了解剩余代码(实际上,整个应用程序和运行时)的作用将导致猜测。
同时,用户模式内存管理器做了两件事:一是确保操作系统分配(也就是“使有效”)虚拟地址空间的一部分,二是细分该部分作为响应到 malloc() 调用。
因此,可以想象(推测)第一次调用 malloc 向 OS 请求虚拟地址空间。不是便宜的电话。第二次调用可能会简单地将指针返回到操作系统返回的空间中,从而使其速度更快。
再次猜测。