相关疑难解决方法(0)

使用 -O3 进行冒泡排序比使用 GCC 的 -O2 慢

我用 C 语言实现了一个冒泡排序,并在测试其性能时发现该-O3标志使其运行速度甚至比没有标志时还要慢!与此同时-O2,它的运行速度比预期的要快得多。

没有优化:

time ./sort 30000

./sort 30000  1.82s user 0.00s system 99% cpu 1.816 total
Run Code Online (Sandbox Code Playgroud)

-O2

time ./sort 30000

./sort 30000  1.00s user 0.00s system 99% cpu 1.005 total
Run Code Online (Sandbox Code Playgroud)

-O3

time ./sort 30000

./sort 30000  2.01s user 0.00s system 99% cpu 2.007 total
Run Code Online (Sandbox Code Playgroud)

代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

int n;

void bubblesort(int *buf)
{
    bool changed = true;
    for (int i = n; changed == true; …
Run Code Online (Sandbox Code Playgroud)

c gcc x86-64 cpu-architecture compiler-optimization

148
推荐指数
1
解决办法
3万
查看次数

为什么 GCC 不能为两个 int32s 的结构生成最佳 operator==?

一位同事向我展示了我认为没有必要的代码,但果然,确实如此。我希望大多数编译器会将所有这三种相等性测试的尝试视为等效的:

#include <cstdint>
#include <cstring>

struct Point {
    std::int32_t x, y;
};

[[nodiscard]]
bool naiveEqual(const Point &a, const Point &b) {
    return a.x == b.x && a.y == b.y;
}

[[nodiscard]]
bool optimizedEqual(const Point &a, const Point &b) {
    // Why can't the compiler produce the same assembly in naiveEqual as it does here?
    std::uint64_t ai, bi;
    static_assert(sizeof(Point) == sizeof(ai));
    std::memcpy(&ai, &a, sizeof(Point));
    std::memcpy(&bi, &b, sizeof(Point));
    return ai == bi;
}

[[nodiscard]]
bool optimizedEqual2(const Point &a, const Point &b) { …
Run Code Online (Sandbox Code Playgroud)

c++ gcc x86-64 micro-optimization compiler-optimization

86
推荐指数
3
解决办法
4658
查看次数

函数调用循环比空循环快

我将一些程序集与一些c链接起来测试函数调用的成本,使用以下程序集和c源代码(分别使用fasm和gcc)

部件:

format ELF

public no_call as "_no_call"
public normal_call as "_normal_call"

section '.text' executable

iter equ 100000000

no_call:
    mov ecx, iter
@@:
    push ecx
    pop ecx
    dec ecx
    cmp ecx, 0
    jne @b
    ret

normal_function:
    ret

normal_call:
    mov ecx, iter
@@:
    push ecx
    call normal_function
    pop ecx
    dec ecx
    cmp ecx, 0
    jne @b
    ret
Run Code Online (Sandbox Code Playgroud)

c来源:

#include <stdio.h>
#include <time.h>

extern int no_call();
extern int normal_call();

int main()
{
    clock_t ct1, ct2;

    ct1 = clock();
    no_call();
    ct2 = clock(); …
Run Code Online (Sandbox Code Playgroud)

c performance x86 assembly fasm

15
推荐指数
1
解决办法
969
查看次数

x86可以重新排序一个具有更宽负载的窄存储吗?

英特尔®64和IA-32架构软件开发人员手册说:

8.2.3.4负载可以通过早期存储
重新排序到不同位置 Intel-64内存排序模型允许将负载与早期存储重新排序到不同位置.但是,加载不会与商店重新排序到同一位置.

那些与之前的商店部分或完全重叠但是没有相同起始地址的负载呢?(有关具体案例,请参阅本文末尾)


假设以下类似C的代码:

// lock - pointer to an aligned int64 variable
// threadNum - integer in the range 0..7
// volatiles here just to show direct r/w of the memory as it was suggested in the comments
int TryLock(volatile INT64* lock, INT64 threadNum)
{
    if (0 != *lock)
        return 0;                           // another thread already had the lock

    ((volatile INT8*)lock)[threadNum] = 1;  // take the lock by setting our byte

    if (1LL << 8*threadNum != …
Run Code Online (Sandbox Code Playgroud)

x86 assembly multithreading locking x86-64

10
推荐指数
2
解决办法
694
查看次数

x86 上存储到加载转发失败的成本是多少?

在最新的 x86 架构上,存储到加载转发失败的成本是多少?

特别是,存储到加载转发会失败,因为加载部分与较早的存储重叠,或者因为较早的加载或存储跨越某些导致转发失败的对齐边界。

当然存在延迟成本:它有多大?是否还存在吞吐量成本,例如,失败的存储到加载转发是否使用了其他加载和存储甚至其他非内存操作无法使用的额外资源?

当存储的所有部分都来自存储缓冲区时,与混合存储缓冲区和 L1 的情况相比,是否有区别?

x86 intel cpu-architecture micro-optimization amd-processor

10
推荐指数
1
解决办法
908
查看次数

存储指令是否会在缓存未命中时阻止后续指令?

假设我们有一个处理器,它有两个内核(C0 和 C1)和一个从k最初由 C0 拥有的地址开始的缓存线。如果 C1 在第 8 字节槽上发出一条存储指令k,是否会影响在 C1 上执行的后续指令的吞吐量?

intel优化手册有以下一段

当一条指令将数据写入内存位置 [...] 时,处理器会确保包含该内存位置的行位于其 L1d 缓存中 [...]。如果缓存线不存在,它会使用 RFO 请求 [...] RFO 从下一级获取数据,并在指令退出后存储数据。因此,存储延迟通常不会影响存储指令本身

参考以下代码,

// core c0
foo();
line(k)->at(i)->store(kConstant, std::memory_order_release);
bar();
baz();
Run Code Online (Sandbox Code Playgroud)

从英特尔手动使得报价我认为在上面的代码,代码的执行将看起来像是商店基本上是一个空操作,和结束之间会不会影响延迟foo()和开始bar()。相比之下,对于下面的代码,

// core c0
foo();
bar(line(k)->at(i)->load(std::memory_order_acquire));
baz();
Run Code Online (Sandbox Code Playgroud)

结束foo()和开始之间的延迟bar()会受到加载的影响,因为以下代码将加载结果作为依赖项。


这个问题主要与英特尔处理器(在 Broadwell 系列或更新版本中)如何在上述情况下工作有关。此外,特别是关于如何将类似于上述的 C++ 代码编译为这些处理器的汇编。

c++ concurrency x86 cpu-architecture cpu-cache

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

将EAX复制到RAX更高位?

我想知道是否有任何指令序列,而不使用任何其他寄存器将RAX的低32位复制到其高32位.当然,我也希望EAX完好无损.

先感谢您.

assembly x86-64

4
推荐指数
3
解决办法
537
查看次数

使用未格式化的数据时,loadu_ps和set_ps有什么区别?

我有一些数据没有存储为数组结构.在寄存器中加载数据的最佳做法是什么?

__m128 _mm_set_ps (float e3, float e2, float e1, float e0) // or __m128 _mm_loadu_ps (float const* mem_addr)

使用_mm_loadu_ps,我将数据复制到临时堆栈数组中,而不是直接将数据复制为值.有区别吗?

sse simd intrinsics sse2

4
推荐指数
1
解决办法
451
查看次数

了解较长代码执行速度提高 4 倍的微架构原因(AMD Zen 2 架构)

我在 x64 模式下使用 VS 2019(版本 16.8.6)编译了以下 C++17 代码:

struct __declspec(align(16)) Vec2f { float v[2]; };
struct __declspec(align(16)) Vec4f { float v[4]; };

static constexpr std::uint64_t N = 100'000'000ull;

const Vec2f p{};
Vec4f acc{};

// Using virtual method:
for (std::uint64_t i = 0; i < N; ++i)
    acc += foo->eval(p);

// Using function pointer:
for (std::uint64_t i = 0; i < N; ++i)
    acc += eval_fn(p);
Run Code Online (Sandbox Code Playgroud)

在第一个循环中,foo是一个std::shared_ptreval()是一个虚方法:

__declspec(noinline) virtual Vec4f eval(const Vec2f& p) const noexcept …
Run Code Online (Sandbox Code Playgroud)

c++ x86 assembly cpu-architecture amd-processor

4
推荐指数
1
解决办法
95
查看次数

为什么错误共享仍然影响非原子,但远小于原子?

考虑以下证明错误共享存在的示例:

\n\n
using type = std::atomic<std::int64_t>;\n\nstruct alignas(128) shared_t\n{\n  type  a;\n  type  b;\n} sh;\n\nstruct not_shared_t\n{\n  alignas(128) type a;\n  alignas(128) type b;\n} not_sh;\n\n
Run Code Online (Sandbox Code Playgroud)\n\n

一个线程a以 1 为步长递增,另一个线程以 1 为步长递增b。增量编译为lock xaddMSVC,即使结果未使用。

\n\n

a对于和分开的结构b,几秒钟内累积的值大约是 的not_shared_t十倍shared_t

\n\n

到目前为止的预期结果:单独的缓存行在 L1d 缓存中保持热状态,增加lock xadd吞吐量瓶颈,错误共享是缓存行乒乓球的性能灾难。(编者注:更高版本的 MSVClock inc在启用优化时使用。这可能会扩大竞争与非竞争之间的差距。)

\n\n
\n\n

现在我using type = std::atomic<std::int64_t>;用普通的替换std::int64_t

\n\n

(非原子增量编译为inc QWORD PTR [rcx]。循环中的原子加载恰好阻止编译器将计数器保留在寄存器中,直到循环退出。)

\n\n

达到的计数not_shared_t仍大于 的计数shared_t,但现在少于两倍。 …

c++ x86 cpu-architecture cpu-cache false-sharing

3
推荐指数
1
解决办法
481
查看次数

为什么矢量长度SIMD代码比普通C慢

为什么我的SIMD vector4长度函数比单纯的向量长度方法慢3倍?

SIMD vector4长度函数:

__extern_always_inline float vec4_len(const float *v) {
    __m128 vec1 = _mm_load_ps(v);
    __m128 xmm1 = _mm_mul_ps(vec1, vec1);
    __m128 xmm2 = _mm_hadd_ps(xmm1, xmm1);
    __m128 xmm3 = _mm_hadd_ps(xmm2, xmm2);
    return sqrtf(_mm_cvtss_f32(xmm3));
}
Run Code Online (Sandbox Code Playgroud)

天真的实现:

sqrtf(V[0] * V[0] + V[1] * V[1] + V[2] * V[2] + V[3] * V[3])
Run Code Online (Sandbox Code Playgroud)

SIMD版本花费了16110ms来迭代10亿次。天真的版本快了约3倍,只花了4746ms。

#include <math.h>
#include <time.h>
#include <stdint.h>
#include <stdio.h>
#include <x86intrin.h>

static float vec4_len(const float *v) {
    __m128 vec1 = _mm_load_ps(v);
    __m128 xmm1 = _mm_mul_ps(vec1, vec1);
    __m128 xmm2 = _mm_hadd_ps(xmm1, xmm1);
    __m128 …
Run Code Online (Sandbox Code Playgroud)

c sse simd compiler-optimization microbenchmark

2
推荐指数
1
解决办法
245
查看次数

为什么我们不能直接从堆栈帧移1个字节到寄存器?

我正在阅读《计算机系统:程序员的观点》,3 / E(CS:APP3e)Randal E. Bryant和David R. O'Hallaron,作者说:“观察到第6行的movl指令从内存中读取了4个字节;以下addb指令仅使用低位字节”

第6行,为什么使用movl?他们为什么不移动8(%rsp),%dl?

void proc(a1, a1p, a2, a2p, a3, a3p, a4, a4p)
Arguments passed as follows:
  a1 in %rdi (64 bits)
  a1p in %rsi (64 bits)
  a2 in %edx (32 bits)
  a2p in %rcx (64 bits)
  a3 in %r8w (16 bits)
  a3p in %r9 (64 bits)
  a4 at %rsp+8 ( 8 bits)
  a4p at %rsp+16 (64 bits)
1   proc:
2   movq    16(%rsp), %rax  Fetch a4p (64 bits)
3   addq    %rdi, (%rsi)    *a1p += a1 (64 …
Run Code Online (Sandbox Code Playgroud)

assembly gcc x86-64 calling-convention function-parameter

2
推荐指数
1
解决办法
64
查看次数

C++ 将快速 1、2、3 整数映射到硬编码字符?

我需要将 int 值 1,2,3 映射到字符 'C', 'M', 'A'

最快的方法是什么(这将被称为每秒 100 次 24/7)?

宏或内联函数和一堆 ?: 运算符或 ifs 或 switch?或者一个数组?

c++ performance

-2
推荐指数
1
解决办法
175
查看次数