【问题标题】:Alignment of pointers to ints in GCC for Intel x64Intel x64 的 GCC 中指向 int 的指针对齐
【发布时间】:2017-01-11 01:26:59
【问题描述】:

我需要将某个内存位置的几个四字加在一起:

uint64_t sum2 (const char * p, size_t n)
{
    uint64_t res = 0;
    const uint32_t * q = (const uint32_t*) p;
    size_t i;
    for (i = 0; i < n; i++) res += q[i];
    return res;
}

我知道这段代码不必在任意机器上的任意 C 编译器上工作。并非每个指向 char 的指针都可以转换为指向 int 的有效指针。但是,在 Intel 上,您可以从任何地址读取 32 位值,在大多数情况下甚至没有任何性能损失,因此无论p 的对齐方式如何,这段代码都应该可以正常工作。我的程序在 64 位 Intel Sandy Bridge 上运行,使用 GCC 4.8 使用 -msse4.2 -O3 编译。

当地址不是 4 对齐时,此代码 SIGSEGV。原因是循环被展开四次并使用 SSE 编译。使用MOVDQA一起读取四个值,需要16对齐。循环之前,指针16对齐,前提是已经对齐4。

如何防止在 GCC 上进行这种 SSE 优化?我真的需要添加未对齐的 32 位数字。

【问题讨论】:

  • 可能使用memcpy 将&amp;q[i] 复制到临时位置。
  • UB 在第二行。为什么要这样做?
  • 它不应该对这段代码进行矢量化,p 没有对齐,并且是任何东西的有效别名。你可以尝试加入 __builtin_assume_aligned(q, 1)。请务必提及版本号。
  • 您是否可以控制 p 的分配方式?如果是这样,只需使用正确的对齐方式进行分配。

标签: c++ pointers alignment


【解决方案1】:

它可能会降低性能,但我认为您需要使用memcpy 复制到正确对齐的临时文件。

uint64_t sum2 (const char * p, size_t n)
{
    uint64_t res = 0, temp;
    const uint32_t * q = (const uint32_t*) p;
    size_t i;
    for (i = 0; i < n; i++) {
        memcpy(&temp, &q[i], sizeof(*q));
        res += temp;
    }
    return res;
}

希望它没有对齐 q 或 &amp;q[i]。如果是这种情况,您需要自己进行地址运算。

uint64_t sum2 (const char * p, size_t n)
{
    uint64_t res = 0, temp;
    size_t i;
    for (i = 0; i < n; i++, p += sizeof(uint32_t)) {
        memcpy(&temp, p, sizeof(uint32_t));
        res += temp;
    }
    return res;
}

【讨论】:

  • memcpy 在 gcc 上作为编译器内部实现,因此编译器可以轻松优化它。
  • memcpy 版本运行得非常好,它生成的代码也非常好。 memcpy 被编译成一条 movl 指令(要注册的内存)。
  • 你需要使用第二个版本吗?
【解决方案2】:

您可以选择性地控制特定功能的优化和代码生成,请参阅 https://gcc.gnu.org/onlinedocs/gcc/Function-Specific-Option-Pragmas.html

和 https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html

特别是:

__attribute__ ((target("no-sse2")))
uint64_t sum2 (const char * p, size_t n)
{
    uint64_t res = 0;
    const uint32_t * q = (const uint32_t*) p;
    size_t i;
    for (i = 0; i < n; i++) res += q[i];
    return res;
}

【讨论】:

  • 谢谢,它有效。小修正:必须引用目标名称并且它应该是 no-sse2,因为生成的代码依赖于 SSE2 指令:attribute ((target("no-sse2")))
【解决方案3】:

以符合标准的方式实现两全其美?

#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>



uint64_t sum2_unaligned (const char * p, size_t n)
{
    uint64_t res = 0, temp;
    const uint32_t * q = (const uint32_t*) p;
    size_t i;
    for (i = 0; i < n; i++) {
        memcpy(&temp, &q[i], sizeof(*q));
        res += temp;
    }
    return res;
}

uint64_t sum2_aligned (const std::uint64_t * p, size_t n)
{
    uint64_t res = 0, temp;
    const uint32_t * q = (const uint32_t*) p;
    size_t i;
    for (i = 0; i < n; i++) {
        res += p[i];
    }
    return res;
}

uint64_t sum2 (const char* p, size_t n)
{
  constexpr auto alignment = alignof(std::uint64_t);
  void* mem = const_cast<void*>(reinterpret_cast<const void*>(p));
  std::size_t space = n * sizeof(std::uint64_t);
  auto mem2 = std::align(alignment, space, mem, space);
  if (mem2 != mem)
  {
    return sum2_unaligned(p, n);
  }
  else
  {
    return sum2_aligned(reinterpret_cast<std::uint64_t const*>(p), n);
  }
}

【讨论】:

    猜你喜欢
    • 2015-11-10
    • 2013-04-17
    • 1970-01-01
    • 2015-07-13
    • 2015-09-10
    • 2017-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多