【问题标题】:Code runs faster using placement new使用placement new 代码运行速度更快
【发布时间】:2012-07-24 18:19:46
【问题描述】:

我上了这门课,

方法一:

typedef float v4sf __attribute__ (vector_size(16))

class Unit
{
    public:
    Unit(int num)
    {
        u = new float[num];
        v = new float[num];
    }
    void update()
    {
        for(int i =0 ; i < num; i+=4)
        {
            *(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
            //many other equations
        }
    }
    float*u,*v,*t; //and many other variables
}

方法2:

与方法 1 相同。除了方法 2 中,vu 和所有其他变量都分配在预先分配在堆上的大块上,使用位置 new

typedef float v4sf __attribute__ (vector_size(16))

class Unit
{
    public:
    Unit(int num)
    {
        buffer = new char[num*sizeof(*u) + sizeof(*v)  /*..and so on for other variables..*/]
        u = new(buffer) float[num];
        v = new(buffer+sizeof(float)*num) float[num];
        //And so on for other variables
    }
    void update()
    {
        for(int i =0 ; i < num; i+=4)
        {
            *(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
            //many other equations
        }
    }
    char* buffer;
    float*u,*v,*t; //and many other variables
}

但是,方法 2 的速度要快 2 倍。这是为什么呢?

大约有 12 个浮点变量,num 为 500K。 update() 被称为1k 次。速度不考虑内存分配。我这样测量速度:

double start = getTime();
for( int i = 0; i < 1000; i++)
{
   unit->update();
}
double end = getTime();
cout<<end - start;

这在方法 2 中快了大约 2 倍。

编译器选项:gcc -msse4 -o3 -ftree-vectorize.

一级缓存为 256K,内存为 8GB,页面大小为 4K。

编辑:更正了方法2中分配变量的错误。所有变量都正确分配在不同的部分。 处理器是 Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz

编辑:在此处添加源代码 - Source。方法 1) 给出 69.58s,方法 2) 给出 46.74s。虽然不是快 2 倍,但它仍然很快。

【问题讨论】:

  • 你能展示方法 2 的实际代码吗?
  • 处理器是什么?我有一种强烈的感觉,你可能会遇到类似的情况:Why is one loop so much slower than two loops?
  • u 和 v 现在是一样的。我认为你应该使用 v = new(buffer + sizeof(float)*num)
  • 对方法 3 进行基准测试会很有趣:使用 std::vector
  • 什么是num?它很大吗?大约 50k - 100k?

标签: c++ performance gcc compiler-optimization


【解决方案1】:

可能是因为“方法 2”有一个错误 - 所有变量 uvt 都位于内存中完全相同的位置(您将相同的地址传递给新位置)。

编辑:现在你不... ;)

如果不进行分析很难猜测,但它可能与默认分配器有关。如果在第一种方法中,您对每个变量都有单独的 new 调用,则不能保证这些变量将分配到彼此接近的地址。另一方面,在第二种方法中,您要确保它们彼此尽可能接近。这将最大限度地提高缓存利用率并限制缓存未命中。

【讨论】:

  • 尤其是数据缓存命中/未命中率可能要高很多。
  • 抱歉,代码错误。所有变量都位于不同且正确的位置。
  • @VivekG 这是否意味着复制和粘贴时出现错误(这就是您所做的,对吗?您不会让我们在错误的代码中追逐问题,是吗?)或原始代码,现在产生不同的结果?
  • 不抱歉,问题是真实存在的。我没有复制粘贴代码,因为它是专有的。我在这里输入代码时犯了错误。
  • @RMF 的意思是:请不要发布与您观察到的内容“相似”的代码,并声称如果没有自己尝试更简单的发布代码的额外步骤,它将有一些行为。在谈论性能时尤其重要!
【解决方案2】:

分解时间并查看构造函数中的部分与update 中的部分会很有用。

由于update 没有改变,唯一会影响它的时间的是缓存对数据的影响。这足以解释 2 倍的差异。

【讨论】:

    【解决方案3】:

    正常的 new 实际上是分配 + 构造,而放置 new 只是构造。
    所以很自然,分配 + 2 构造比分配 + 构造 + 分配 + 构造更快。
    此外,整数类型的构造是 nop,所以在您的情况下,它是 2 个分配与 1 个分配。

    【讨论】:

      【解决方案4】:

      我假设在方法 2 中,编译器能够识别 u 和 v 的地址在调用之间不会改变,因此将 for 循环中方程式中使用的一些指针保留在寄存器中。

      【讨论】:

        猜你喜欢
        • 2017-02-21
        • 2022-01-01
        • 2012-08-04
        • 2015-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-12
        • 2021-08-22
        相关资源
        最近更新 更多