相关疑难解决方法(0)

为什么在强度降低乘法和循环进位加法之后,这段代码的执行速度会变慢?

我正在阅读Agner Fog优化手册,并且遇到了这个例子:

double data[LEN];

void compute()
{
    const double A = 1.1, B = 2.2, C = 3.3;

    int i;
    for(i=0; i<LEN; i++) {
        data[i] = A*i*i + B*i + C;
    }
}
Run Code Online (Sandbox Code Playgroud)

Agner 指出,有一种方法可以优化此代码 - 通过认识到循环可以避免使用昂贵的乘法,而是使用每次迭代应用的“增量”。

我用一张纸来证实这个理论,首先......

理论

...当然,他是对的 - 在每次循环迭代中,我们可以通过添加“增量”,基于旧结果计算新结果。该增量从值“A+B”开始,然后每一步增加“2*A”。

所以我们将代码更新为如下所示:

void compute()
{
    const double A = 1.1, B = 2.2, C = 3.3;
    const double A2 = A+A;
    double Z = A+B;
    double Y = C;

    int i;
    for(i=0; i<LEN; i++) {
        data[i] …
Run Code Online (Sandbox Code Playgroud)

optimization assembly x86-64 simd cpu-architecture

320
推荐指数
6
解决办法
9万
查看次数

为什么32字节的循环对齐使代码更快?

看看这段代码:

one.cpp:

bool test(int a, int b, int c, int d);

int main() {
        volatile int va = 1;
        volatile int vb = 2;
        volatile int vc = 3;
        volatile int vd = 4;

        int a = va;
        int b = vb;
        int c = vc;
        int d = vd;

        int s = 0;
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop");
        for (int i=0; i<2000000000; i++) {
                s += test(a, b, …
Run Code Online (Sandbox Code Playgroud)

performance benchmarking gcc x86-64 clang

12
推荐指数
1
解决办法
735
查看次数

与 GCC 5 相比,使用 GCC 9 编译的 STLpriority_queue 性能较慢

对于我的项目,我从 GCC 5 切换到 GCC 9,发现性能变得更差。我做了一些调查并提出了一个简单的源代码来重现该行为。

我在同一台机器上使用不同的 GCC 版本(g++-5 和 g++-9)编译代码

#include <queue>

int main()
{
        std::priority_queue<int> q;
        for (int j = 0; j < 2000; j ++) {
                for (int i = 0; i < 20000; i ++) {
                        q.emplace(i);
                }
                for (int i = 0; i < 20000; i ++) {
                        q.pop();
                }
        }
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

当我使用 GCC 5 编译它时,我得到以下计时:

# g++-5 -std=c++14 -O3 main.cpp
# time ./a.out

real    0m1.580s
user    0m1.578s
sys     0m0.001s
Run Code Online (Sandbox Code Playgroud)

对 GCC 9 …

c++ performance gcc priority-queue compiler-optimization

11
推荐指数
1
解决办法
312
查看次数

32 字节对齐例程不适合 uops 缓存

KbL i7-8550U

我正在研究 uops-cache 的行为并遇到了关于它的误解。

如英特尔优化手册2.5.2.2(我的)中所述:

解码的 ICache 由 32 组组成。每组包含八种方式。 每路最多可容纳六个微操作。

——

Way 中的所有微操作表示在代码中静态连续的指令,并且它们的 EIP 位于相同的对齐 32 字节区域内。

——

最多三种方式可以专用于相同的 32 字节对齐块,从而允许在原始 IA 程序的每个 32 字节区域中缓存总共 18 个微操作。

——

无条件分支是 Way 中的最后一个微操作。


情况1:

考虑以下例程:

uop.h

void inhibit_uops_cache(size_t);
Run Code Online (Sandbox Code Playgroud)

uop.S

align 32
inhibit_uops_cache:
    mov edx, esi
    mov edx, esi
    mov edx, esi
    mov edx, esi
    mov edx, esi
    mov edx, esi
    jmp decrement_jmp_tgt
decrement_jmp_tgt:
    dec rdi
    ja inhibit_uops_cache ;ja is intentional to avoid Macro-fusion
    ret
Run Code Online (Sandbox Code Playgroud)

为了确保例程的代码实际上是 32 字节对齐的,这里是 asm …

performance x86 assembly intel cpu-architecture

7
推荐指数
1
解决办法
483
查看次数

如何确定 gcc 支持哪些架构?

GCC 支持-march允许您指定目标架构的开关 - 允许它调整该平台的指令序列以及使用该平台上可能可用但在“默认”或基本版本上不可用的指令架构。

例如,-march=skylake将告诉编译器以 Skylake CPU 为目标,包括使用 Skylake 上可用的指令集,例如 AVX2。

如何判断-march本地版本gcc支持的值?当传递无效参数时,较新版本有助于列出有效参数,但较旧版本不会。

performance x86 gcc

6
推荐指数
1
解决办法
5549
查看次数

当跳转在 32 字节上没有完全对齐时,使用 MITE(传统管道)代替 DSB(uop 缓存)

这个问题曾经是这个(现已更新)问题的一部分,但它似乎应该是另一个问题,因为它无助于获得另一个问题的答案。


我的出发点是一个循环进行 3 个独立的添加:

for (unsigned long i = 0; i < 2000000000; i++) {
    asm volatile("" : "+r" (a), "+r" (b), "+r" (c), "+r" (d)); // prevents C compiler from optimizing out adds
    a = a + d;
    b = b + d;
    c = c + d;
}
Run Code Online (Sandbox Code Playgroud)

当这个循环没有展开时,它在 1 个周期内执行(这是预期的:它包含 4 条指令:3 个加法和宏融合增量/跳转;所有这些都可以在端口 0 上在一个周期内执行, 1、5 和 6)。展开此循环时,性能令人惊讶,并且往往比未展开的版本慢 25%,这可能是由于 uops 调度,如上一个问题的评论中所建议的。

在这个问题中,我不是在问性能,而是在问为什么在某些情况下,uop 来自 MITE(传统管道),而在其他情况下,来自 DSB(uop 缓存)。(请注意,我使用的是禁用 LSD(循环流检测器)的 Skylake)

实验上,当跳转在 32 字节上没有完全对齐时,uop 是从 MITE …

performance x86 assembly intel

6
推荐指数
1
解决办法
205
查看次数

代码对齐会显着影响性能

今天我发现示例代码在添加了一些不相关的代码后速度降低了 50%。调试后我发现问题出在循环对齐上。根据循环代码的放置,有不同的执行时间,例如:

地址 时间[我们]
00007FF780A01270 980us
00007FF7750B1280 1500us
00007FF7750B1290 986us
00007FF7750B12A0 1500us

之前没想到代码对齐会有这么大的影响。我认为我的编译器足够聪明,可以正确对齐代码。

究竟是什么导致了执行时间的如此大的差异?(我想一些处理器架构细节)。

我用 Visual Studio 2019 在 Release 模式下编译的测试程序并在 Windows 10 上运行它。我在 2 个处理器上检查了程序:i7-8700k(上面的结果)和 intel i5-3570k 但问题不存在在那里,执行时间总是大约 1250us。我也试过用 clang 编译程序,但结果总是 ~1500us(在 i7-8700k 上)。

我的测试程序:

#include <chrono>
#include <iostream>
#include <intrin.h>
using namespace std;

template<int N>
__forceinline void noops()
{
    __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
    noops<N - 1>();
}
template<>
__forceinline void noops<0>(){}

template<int OFFSET>
__declspec(noinline) void SumHorizontalLine(const …
Run Code Online (Sandbox Code Playgroud)

c++ performance x86 assembly micro-optimization

6
推荐指数
1
解决办法
233
查看次数

将 9 个字符数字转换为 int 或 unsigned int 的最疯狂的快速方法

#include <stdio.h>
#include <iostream>
#include <string>
#include <chrono>
#include <memory>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <immintrin.h>
using namespace std;

const int p[9] =   {1, 10, 100, 
                    1000, 10000, 100000, 
                    1000000, 10000000, 100000000};
                    
class MyTimer {
 private:
  std::chrono::time_point<std::chrono::steady_clock> starter;

 public:
  void startCounter() {
    starter = std::chrono::steady_clock::now();
  }

  int64_t getCounterNs() {    
    return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - starter).count();
  }
};
                    
int convert1(const char *a) {
    int res = 0;
    for (int i=0; i<9; i++) res = res * 10 + a[i] - 48; …
Run Code Online (Sandbox Code Playgroud)

c++ optimization assembly sse x86-64

6
推荐指数
2
解决办法
1363
查看次数

为什么 gcc -O3 会生成多个 ret 指令?

我正在从这里查看一些递归函数:

int get_steps_to_zero(int n)
{
    if (n == 0) {
        // Base case: we have reached zero
        return 0;
    } else if (n % 2 == 0) {
        // Recursive case 1: we can divide by 2
        return 1 + get_steps_to_zero(n / 2);
    } else {
        // Recursive case 2: we can subtract by 1
        return 1 + get_steps_to_zero(n - 1);
    }
}
Run Code Online (Sandbox Code Playgroud)

我检查了反汇编,以检查 gcc 是否管理尾部调用优化/展开。看起来确实如此,尽管使用 x86-64 gcc 12.2 -O3 我得到了一个像这样的函数,以两条ret指令结尾:

get_steps_to_zero:
        xor     eax, eax
        test    edi, …
Run Code Online (Sandbox Code Playgroud)

c assembly gcc x86-64 micro-optimization

6
推荐指数
1
解决办法
239
查看次数

32 位比较比 64 位比较快吗?

32位的比较比64位的比较快吗?

我在看这个文件http://www.netlib.org/fdlibm/s_cos.c

他们有这段代码

    /* |x| ~< pi/4 */
    ix &= 0x7fffffff;
    if(ix <= 0x3fe921fb) return __kernel_cos(x,z);
Run Code Online (Sandbox Code Playgroud)

我理解第一行,它计算x的绝对值。但为什么比较如此复杂?通过比较前 32 位而不是全部 64 位,性能是否有任何改进?我能写吗

long unsigned int ix = *(long unsigned int * (&x));
ix &= 0x7fffffffffffffff;
if (ix < 3fe921fb54442d18) 
/* What comes next */
Run Code Online (Sandbox Code Playgroud)

并期望在 64 位机器上获得相同的速度性能?虽然我同意这会消耗更多内存。

0x3fe921fb54442d18 是 pi/2。

c

5
推荐指数
1
解决办法
85
查看次数