天道酬勤,学无止境

Why was no intrinsic access to the CPU's status register in the design of both C and C++?

In the case of the overflow flag, it would seem that access to this flag would be a great boon to cross-architecture programming. It would provide a safe alternative to relying on undefined behaviour to check for signed integer overflow such as:

if(a < a + 100) //detect overflow

I do understand that there are safe alternatives such as:

if(a > (INT_MAX - 100)) //detected overflow

However, it would seem that access to the status register or the individual flags within it is missing from both the C and C++ languages. Why was this feature not included or what language design decisions were made that prohibited this feature from being included?

评论

Because C and C++ are designed to be platform independent. Status register is not.

These days, two's complement is universally used to implement signed integer arithmetic, but it was not always the case. One's complement or sign and absolute value used to be quite common. And when C was first designed, such CPUs were still in common use. E.g. COBOL distinguishes negative and positive 0, which existed on those architectures. Obviously overflow behaviour on these architectures is completely different!

By the way, you can't rely on undefined behaviour for detecting overflow, because reasonable compilers upon seeing

if(a < a + 100)

will write a warning and compile

if(true)

... (provided optimizations are turned on and the particular optimization is not turned off).

And note, that you can't rely on the warning. The compiler will only emit the warning when the condition ends up true or false after equivalent transformations, but there are many cases where the condition will be modified in presence of overflow without ending up as plain true/false.

  • Because C++ is designed as a portable language, i.e. one that compiles on many CPUs (e.g. x86, ARM, LSI-11/2, with devices like Game Boys, Mobile Phones, Freezers, Airplanes, Human Manipulation Chips and Laser Swords).
    • the available flags across CPUs may largely differ
    • even within the same CPU, flags may differ (take x86 scalar vs. vector instructions)
    • some CPUs may not even have the flag you desire at all
  • The question has to be answered: Should the compiler always deliver/enable that flag when it can't determine whether it is used at all?, which does not conform the pay only for what you use unwritten but holy law of both C and C++
  • Because compilers would have to be forbidden to optimize and e.g. reorder code to keep those flags valid

Example for the latter:

int x = 7;
x += z;
int y = 2;
y += z;

The optimizer may transform this to that pseudo assembly code:

alloc_stack_frame 2*sizeof(int)
load_int 7, $0
load_int 2, $1
add z, $0
add z, $1

which in turn would be more similar to

int x = 7;
int y = 2;
x += z;
y += z; 

Now if you query registers inbetween

int x = 7;
x += z;
if (check_overflow($0)) {...}
int y = 2;
y += z;

then after optimizing and dissasembling you might end with this:

int x = 7;
int y = 2;
x += z;
y += z;
if (check_overflow($0)) {...}

which is then incorrect.

More examples could be constructed, like what happens with a constant-folding-compile-time-overflow.


Sidenotes: I remember an old Borland C++ compiler having a small API to read the current CPU registers. However, the argumentation above about optimization still applies.

On another sidenote: To check for overflow:

// desired expression: int z = x + y
would_overflow = x > MAX-y;

more concrete

auto would_overflow = x > std::numeric_limits<int>::max()-y;

or better, less concrete:

auto would_overflow = x > std::numeric_limits<decltype(x+y)>::max()-y;

I can think of the following reasons.

  1. By allowing access to the register-flags, portability of the language across platforms is severily limited.

  2. The optimizer can change expressions drastically, and render your flags useless.

  3. It would make the language more complex

  4. Most compilers have a big set of intrinsic functions, to do most common operations (e.g. addition with carry) without resorting to flags.

  5. Most expressions can be rewritten in a safe way to avoid overflows.

  6. You can always fall back to inline assembly if you have very specific needs

Access to status registers does not seem needed enough, to go through a standardization-effort.

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • SIMD via C#
    简介 TL;DR我们为C#(准确地说是.NET Core)引入了一套全新的机制,使得C# 以后可以像C/C++ 一样直接使用intrinsic functions 来直接操作Intel CPU 的大多数SIMD 指令了(从SSE 到AVX2)。(注意是以后!这个项目还没有完成!)Vectors in .NET在最开始我想先说一说SIMD 编程在C#/.NET 中的现状,以及为什么我们要引入这套全新的intrinsic。微软在之前的.NET Framework 和.NET Core 中引入了一个新的库: System.Numerics.Vectors ,其中包含几个重要的值类型(Vector<T>, Vector2, Vector3, 等等)和操作它们的一些静态方法。程序员可以用这个库在.NET 环境中编写SIMD 程序。以下我假定大家都大概知道SIMD 编程的概念,来具体讲讲这个库 的设计与实现。System.Numerics.Vectors 库中的这些静态方法的实际功能不能用C# 等.NET managed language 直接写出来(虽然它们都有一份C# 的实现),而是由编译器特殊对待从而生成特殊代码(SSE, AVX, AVX2, 等指令集的指令),我们称这些方法(函数)为intrinsic。这些intrinsic 大部分都操作在上面说到的这些值类型上(Vector<T>
  • How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?
    I don't have a particular use-case in mind; I'm asking if this is really a design flaw / limitation in Intel's intrinsics or if I'm just missing something. If you want to combine a scalar float with an existing vector, there doesn't seem to be a way to do it without high-element-zeroing or broadcasting the scalar into a vector, using Intel intrinsics. I haven't investigated GNU C native vector extensions and the associated builtins. This wouldn't be too bad if the extra intrinsic optimized away, but it doesn't with gcc (5.4 or 6.2). There's also no nice way to use pmovzx or insertps as loads
  • 如何将标量合并到向量中,而无需编译器浪费指令将上位元素清零? 英特尔固有特性中的设计限制?(How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?)
    问题 我没有特定的用例; 我在问这是否真的是英特尔内部特性的设计缺陷/局限性,或者我是否只是缺少一些东西。 如果要将标量浮点数与现有矢量结合起来,似乎没有办法使用Intel内部函数将其进行高元素置零或将标量广播到矢量中。 我还没有研究过GNU C本机向量扩展和相关的内建函数。 如果将额外的内在函数优化掉,这并不太糟,但对于gcc(5.4或6.2)则不是这样。 也没有很好的方法将pmovzx或insertps用作负载,原因是它们的内在函数仅采用向量args。 (而且gcc不会将标量->矢量加载折叠到asm指令中。) __m128 replace_lower_two_elements(__m128 v, float x) { __m128 xv = _mm_set_ss(x); // WANTED: something else for this step, some compilers actually compile this to a separate insn return _mm_shuffle_ps(v, xv, 0); // lower 2 elements are both x, and the garbage is gone } gcc 5.3 -march = nehalem -O3输出,以启用SSE4.1并针对该Intel CPU进行调整:(如果没有SSE4.1
  • 缺少掩码的 AVX-512 内在函数?(Missing AVX-512 intrinsics for masks?)
    问题 Intel 的内在函数指南列出了 AVX-512 K* 掩码指令的许多内在函数,但似乎缺少一些: KSHIFT{左/右} 卡德测试 英特尔开发人员手册声称内在函数不是必需的,因为它们是由编译器自动生成的。 但是如何做到这一点呢? 如果这意味着 __mmask* 类型可以被视为常规整数,那将很有意义,但是测试诸如mask << 4类的东西似乎会导致编译器将掩码移动到常规寄存器,将其移位,然后移回到面具。 这是使用 Godbolt 最新的 GCC 和带有-O2 -mavx512bw ICC 进行-O2 -mavx512bw 。 同样有趣的是,内在函数只处理__mmask16而不是其他类型。 我没有进行太多测试,但看起来 ICC 不介意采用不正确的类型,但 GCC 似乎确实尝试确保掩码中只有 16 位,如果您使用内在函数。 我是否没有查看上述指令的正确内在函数以及其他 __mmask* 类型变体,或者是否有另一种方法可以在不诉诸内联汇编的情况下实现相同的目标? 回答1 英特尔的文档说,“没有必要,因为它们是由编译器自动生成的”实际上是正确的。 然而,它并不令人满意。 但要了解为什么会这样,您需要查看 AVX512 的历史。 虽然这些信息都不是官方的,但它是根据证据强烈暗示的。 掩码内在函数的状态陷入混乱的原因可能是因为 AVX512 在多个阶段“推出”
  • SSE 指令 MOVSD(扩展:x86、x86-64 上的浮点标量和向量运算)(SSE instruction MOVSD (extended: floating point scalar & vector operations on x86, x86-64))
    问题 我对 MOVSD 汇编指令不知何故感到困惑。 我写了一些计算矩阵乘法的数字代码,只是使用没有 SSE 内在函数的普通 C 代码。 我什至不包括用于编译的 SSE2 内在函数的头文件。 但是当我检查汇编器输出时,我看到: 1)使用128位向量寄存器XMM; 2) 调用SSE2 指令MOVSD。 我知道 MOVSD 本质上是在单双精度浮点上运行的。 它只使用 XMM 寄存器的低 64 位并将高 64 位设置为 0。但我只是不明白两件事: 1) 我从来没有给编译器任何使用 SSE2 的提示。 另外,我使用的是 GCC 而不是英特尔编译器。 据我所知,intel 编译器会自动寻找向量化的机会,但 GCC 不会。 那么 GCC 怎么知道使用 MOVSD 的呢?? 或者,这个 x86 指令早在 SSE 指令集之前就已经存在了,而 SSE2 中的 _mm_load_sd() 内在函数只是为使用 XMM 寄存器进行标量计算提供向后兼容性? 2)为什么编译器不使用其他浮点寄存器,要么是80位浮点栈,要么是64位浮点寄存器?? 为什么必须使用 XMM 寄存器(通过设置高位 64 位 0 并实质上浪费该存储)? XMM 是否提供更快的访问? 顺便说一下,我还有一个关于 SSE2 的问题。 我只是看不出 _mm_store_sd() 和 _mm_storel_sd() 之间的区别。 两者都将较低的
  • VS:_BitScanReverse64 内在的意外优化行为(VS: unexpected optimization behavior with _BitScanReverse64 intrinsic)
    问题 以下代码在调试模式下工作正常,因为 _BitScanReverse64 被定义为在未设置位时返回 0。 引用 MSDN:(返回值是)“如果设置了索引,则为非零,如果未找到设置位,则为 0。” 如果我在发布模式下编译此代码,它仍然有效,但如果我启用编译器优化,例如 \O1 或 \O2,则索引不为零并且assert()失败。 #include <iostream> #include <cassert> using namespace std; int main() { unsigned long index = 0; _BitScanReverse64(&index, 0x0ull); cout << index << endl; assert(index == 0); return 0; } 这是预期的行为吗? 我使用的是 Visual Studio Community 2015,版本 14.0.25431.01 更新 3。(我留下了 cout,以便在优化过程中不会删除变量索引)。 还有一个有效的解决方法,还是我不应该直接使用这个编译器内在的? 回答1 AFAICT,当输入为零时,内在函数将垃圾留在index ,弱于 asm 指令的行为。 这就是为什么它有一个单独的布尔返回值和整数输出操作数。 尽管index arg 是通过引用获取的,但编译器将其视为仅输出。 unsigned
  • RGBA到ABGR:适用于iOS / Xcode的嵌入式机械臂霓虹灯asm(RGBA to ABGR: Inline arm neon asm for iOS/Xcode)
    问题 该代码(非常相似的代码,没有完全尝试过此代码)使用Android NDK进行编译,但不能使用Xcode / armv7 + arm64 / iOS进行编译 评论错误: uint32_t *src; uint32_t *dst; #ifdef __ARM_NEON __asm__ volatile( "vld1.32 {d0, d1}, [%[src]] \n" // error: Vector register expected "vrev32.8 q0, q0 \n" // error: Unrecognized instruction mnemonic "vst1.32 {d0, d1}, [%[dst]] \n" // error: Vector register expected : : [src]"r"(src), [dst]"r"(dst) : "d0", "d1" ); #endif 此代码有什么问题? 编辑1: 我使用内部函数重写了代码: uint8x16_t x = vreinterpretq_u8_u32(vld1q_u32(src)); uint8x16_t y = vrev32q_u8(x); vst1q_u32(dst, vreinterpretq_u32_u8(y)); 拆卸后,我得到以下内容,这是我已经尝试过的变体: vld1.32 {d16
  • 有没有比添加 0.5f 和截断转换更直接的方法来将 float 转换为 int 并进行舍入?(Is there a more direct method to convert float to int with rounding than adding 0.5f and converting with truncation?)
    问题 在处理浮点数据的 C++ 代码中,从 float 到 int 的舍入转换经常发生。 例如,一种用途是生成转换表。 考虑这个代码片段: // Convert a positive float value and round to the nearest integer int RoundedIntValue = (int) (FloatValue + 0.5f); C/C++ 语言将 (int) 转换定义为截断,因此必须添加 0.5f 以确保向上舍入到最接近的正整数(当输入为正时)。 对于以上,VS2015的编译器生成如下代码: movss xmm9, DWORD PTR __real@3f000000 // 0.5f addss xmm0, xmm9 cvttss2si eax, xmm0 上述工作,但可能更有效...... 英特尔的设计者显然认为用一条指令解决一个问题已经足够重要了:转换为最接近的整数值:cvtss2si(注意,在助记符中只有一个“t”)。 如果 cvtss2si 替换上述序列中的 cvttss2si 指令,则三个指令中的两个将被消除(就像使用额外的 xmm 寄存器一样,这可以导致更好的整体优化)。 那么我们如何编写 C++ 语句来使用一条 cvtss2si 指令完成这项简单的工作呢? 我一直在四处探索,尝试以下操作,但即使使用优化器执行任务
  • SSE逻辑内在函数之间有什么区别?(What's the difference between logical SSE intrinsics?)
    问题 不同类型的逻辑SSE内部函数之间有什么区别? 例如,如果我们进行“或”运算,则存在三个内在函数:_mm_or_ps,_mm_or_pd和_mm_or_si128,它们都具有相同的作用:计算其操作数的按位或。 我的问题: 使用一个或另一个内在函数(使用适当的类型转换)之间有什么区别。 在某些特定情况下,不会存在诸如更长的执行时间之类的隐性成本吗? 这些内在函数映射到三个不同的x86指令(por,orps,orpd)。 有谁知道英特尔为什么要浪费宝贵的操作码空间来执行同一条指令? 回答1 我认为这三者实际上是相同的,即128位按位运算。 存在不同形式的原因可能是历史原因,但我不确定。 我猜可能在浮点版本中可能会有一些其他行为,例如,当存在NaN时,但这纯粹是猜测。 对于普通输入,指令似乎可以互换,例如 #include <stdio.h> #include <emmintrin.h> #include <pmmintrin.h> #include <xmmintrin.h> int main(void) { __m128i a = _mm_set1_epi32(1); __m128i b = _mm_set1_epi32(2); __m128i c = _mm_or_si128(a, b); __m128 x = _mm_set1_ps(1.25f); __m128 y =
  • 如果它们是 16 字节对齐的,是否可以将浮点数直接转换为 __m128?(Is it possible to cast floats directly to __m128 if they are 16 byte aligned?)
    问题 如果它们是 16 字节对齐的,将浮点数直接转换为__m128是否安全/可能/建议? 我注意到使用_mm_load_ps和_mm_store_ps来“包装”原始数组会增加大量开销。 我应该注意哪些潜在的陷阱? 编辑 : 使用加载和存储指令实际上没有开销,我得到了一些混合的数字,这就是我获得更好性能的原因。 即使你能够在__m128实例中对原始内存地址进行一些可怕的__m128 ,当我运行测试时,在没有_mm_load_ps指令的情况下,它花费了_mm_load_ps来完成,可能会回退到一些故障安全代码路径。 回答1 是什么让您认为_mm_load_ps和_mm_store_ps “增加了大量开销”? 假设源/目标是内存,这是向/从 SSE 寄存器加载/存储浮点数据的正常方法(无论如何,任何其他方法最终都归结为这一点)。 回答2 有几种方法可以将float值放入 SSE 寄存器; 可以使用以下内在函数: __m128 sseval; float a, b, c, d; sseval = _mm_set_ps(a, b, c, d); // make vector from [ a, b, c, d ] sseval = _mm_setr_ps(a, b, c, d); // make vector from [ d, c, b, a ] sseval = _mm_load_ps(
  • Visual Studio 2010 - 2015 不使用 ymm* 寄存器进行 AVX 优化(Visual Studio 2010 - 2015 does not use ymm* registers for AVX optimization)
    问题 我的笔记本电脑 CPU 仅支持 AVX(高级矢量扩展)但不支持 AVX2。 对于 AVX,128 位 xmm* 寄存器已经扩展到 256 位 ymm* 寄存器以进行浮点运算。 但是,我已经测试过所有版本的 Visual Studio(从 2010 年到 2015 年)都没有在 /arch:AVX 优化下使用 ymm* 寄存器,尽管它们在 /arch:AVX2 优化下这样做了。 下面显示了一个简单的 for 循环的反汇编。 该程序在发布版本中使用 /arch:AVX 编译,所有优化选项都打开。 float a[10000], b[10000], c[10000]; for (int x = 0; x < 10000; x++) 1000988F xor eax,eax 10009891 mov dword ptr [ebp-9C8Ch],ecx c[x] = (a[x] + b[x])*b[x]; 10009897 vmovups xmm1,xmmword ptr c[eax] 100098A0 vaddps xmm0,xmm1,xmmword ptr c[eax] 100098A9 vmulps xmm0,xmm0,xmm1 100098AD vmovups xmmword ptr c[eax],xmm0 100098B6 vmovups xmm1,xmmword ptr
  • SSE instruction MOVSD (extended: floating point scalar & vector operations on x86, x86-64)
    I am somehow confused by the MOVSD assembly instruction. I wrote some numerical code computing some matrix multiplication, simply using ordinary C code with no SSE intrinsics. I do not even include the header file for SSE2 intrinsics for compilation. But when I check the assembler output, I see that: 1) 128-bit vector registers XMM are used; 2) SSE2 instruction MOVSD is invoked. I understand that MOVSD essentially operates on single double precision floating point. It only uses the lower 64-bit of an XMM register and set the upper 64-bit 0. But I just don't understand two things: 1) I never
  • 编码实践,使编译器/优化器可以制作更快的程序(Coding Practices which enable the compiler/optimizer to make a faster program)
    问题 许多年前,C编译器并不是特别聪明。 作为一种解决方法,K&R发明了register关键字,以向编译器提示,将这个变量保留在内部寄存器中可能是一个好主意。 他们还让第三级操作员帮助生成更好的代码。 随着时间的流逝,编译器逐渐成熟。 他们变得非常聪明,因为他们的流程分析使他们能够比您可能做的更好地决定要保存在寄存器中的值。 register关键字变得不重要。 由于别名问题,对于某些类型的操作,FORTRAN可能比C更快。 从理论上讲,经过仔细的编码,可以克服这一限制,以使优化器生成更快的代码。 有哪些可用的编码实践可以使编译器/优化器生成更快的代码? 确定您使用的平台和编译器,将不胜感激。 为什么该技术似乎有效? 鼓励使用示例代码。 这是一个相关的问题 [编辑]此问题与概要分析和优化的总体过程无关。 假设程序已正确编写,经过全面优化编译,经过测试并投入生产。 您的代码中可能存在一些禁止优化器尽其所能的构造。 您如何做才能重构以消除这些禁止并允许优化器生成更快的代码? [编辑]偏移相关链接 回答1 写局部变量而不是输出参数! 这对于解决混叠速度降低可能是一个巨大的帮助。 例如,如果您的代码看起来像 void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut) { for (int i=0; i
  • C ++中最大的整数数据类型?(biggest integer datatype in c++?)
    问题 哪个是 C++ 中最大的整数数据类型? 回答1 最大的标准C++ 整数类型是long 。 C 有一个long long ,C++0x 也将添加它,当然您可以实现自己的自定义整数类型,甚至可能是 BigInt 类。 但从技术上讲,考虑到内置整数类型, long是您的答案。 回答2 long long数据类型是标准 C99 和 C++0x 中最大的内置整数数据类型。 与所有其他整数数据类型一样, long long没有以字节为单位给出确切的大小。 相反,它被定义为至少一个 64 位整数。 虽然long long不是官方 C++ 标准的一部分,但现代编译器普遍支持它。 但是请注意,许多现代台式机的编译器将long和long long定义为恰好 64 位,而许多嵌入式处理器的编译器将long定义为 32 位, long long定义为 64 位(显然有一些例外)。 如果您需要更高的精度或绝对不能使用long long语言扩展,您将不得不使用 C 或 C++ 库之一,这些库旨在处理极大或极小的数字。 回答3 您可能更愿意通过通过<cstdint>及其类型定义intmax_t和uintmax_t映射到编译架构上的最大(完全实现)类型来避免担心原始名称。 我很惊讶没有其他人这么说,但粗略的研究表明它是在 C++11 中添加的,这可能可以解释之前没有提到的原因。 (虽然它的同胞新的原始
  • Fortran:整数*4 vs 整数(4) vs 整数(种类=4)(Fortran: integer*4 vs integer(4) vs integer(kind=4))
    问题 我正在尝试学习 Fortran 并且我看到很多不同的定义被传递,我想知道他们是否正在尝试完成同样的事情。 以下有什么区别? integer*4 integer(4) integer(kind=4) 回答1 在 Fortran >=90 中,最好的方法是使用内在函数来指定您需要的精度——这既保证了可移植性,又保证了您获得所需的精度。 例如,要获得至少支持 8 个十进制数字的整数i和my_int ,您可以使用: integer, parameter :: RegInt_K = selected_int_kind (8) integer (kind=RegInt_K) :: i, my_int 将RegInt_K (或您选择的任何名称)定义为parameter ,您可以在整个代码中将其用作符号。 这也使更改精度变得容易。 请求 8 位或 9 位十进制数字通常会获得一个 4 字节整数。 integer*4是一个常见的扩展,可以追溯到旧的 FORTRAN 来指定一个 4 字节的整数。 虽然,这种语法不是也从来不是标准的 Fortran。 integer (4)或integer (RegInt_K)是integer (kind=4)或integer (kind=RegInt_K) 。 integer (4)与integer*4并且是不可移植的——语言标准没有指定种类的数值。
  • 华为硬件逻辑岗笔试题(一)
    积少成多,集腋成裘,坚持!!! 目录 1. 进制转换 2. 状态机和编码方式 3. 存储器的分类 4. Verilog语法中的操作符 5. 对组合逻辑的认识 6. 对时序逻辑的认识 7. 竞争冒险的认识 8. 基本时序逻辑电路 9. 建立时间和保持时间 10. 同步时序电路 11. 组合逻辑和时序逻辑判断 13. 基本总线的理解 14.加法器 15.FPGA开发工具 1. 进制转换 1、十进制46.25对应的二进制表达式为( )。 A 101110.11 B 101101.01 C 101110.1 D 101110.01 考点:整数部分:除基逆取余,乘基顺取整。 2. 状态机和编码方式 2、在时序电路的状态转换表中,若状态数N=3,则状态变量数最少为( ) A 4 B 8 C 2 D 16 考点:状态机和编码方式(格雷码,独热码,二进制码) 为什么例子中我们使用的是独热码而非二进制码或格雷码呢?那就要从每种编码的特性上说起了,首先独热码因为每个状态只有1bit是不同的,所以在执行到43行时的(state == TWO)这条语句时,综合器会识别出这是一个比较器,而因为只有1比特为1,所以综合器会进行智能优化为(state[2] == 1’b1),这就相当于把之前3比特的比较器变为了1比特的比较器,大大节省了组合逻辑资源,但是付出的代价就是状态变量的位宽需要的比较多
  • SSE:_mm_load/store 与使用直接指针访问的区别(SSE: Difference between _mm_load/store vs. using direct pointer access)
    问题 假设我想添加两个缓冲区并存储结果。 两个缓冲区都已分配为 16 字节对齐。 我找到了两个如何做到这一点的例子。 第一个是使用 _mm_load 将缓冲区中的数据读入 SSE 寄存器,执行加法操作并存储回结果寄存器。 直到现在我都会这样做。 void _add( uint16_t * dst, uint16_t const * src, size_t n ) { for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 ) { __m128i _s = _mm_load_si128( (__m128i*) src ); __m128i _d = _mm_load_si128( (__m128i*) dst ); _d = _mm_add_epi16( _d, _s ); _mm_store_si128( (__m128i*) dst, _d ); } } 第二个例子只是直接对内存地址进行加法操作,没有加载/存储操作。 两个接缝都能正常工作。 void _add( uint16_t * dst, uint16_t const * src, size_t n ) { for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 ) { *(_
  • 对齐和未对齐的内存访问?(Aligned and unaligned memory accesses?)
    问题 对齐和未对齐的内存访问有什么区别? 我在TMS320C64x DSP上工作,我想使用内在函数(用于汇编指令的C函数),并且它具有 ushort & _amem2(void *ptr); ushort & _mem2(void *ptr); _amem2执行2字节的对齐访问,而_mem2执行未对齐的访问。 我什么时候应该使用哪个? 回答1 对齐的内存访问意味着指针(作为整数)是称为对齐的类型特定值的倍数。 对齐方式是类型必须是或应该(例如出于性能原因)存储在CPU上的自然地址倍数。 例如,CPU可能要求所有两个字节的加载或存储都必须通过2的倍数进行。 对于较小的原始类型(小于4个字节),对齐几乎总是该类型的大小。 对于结构,对齐方式通常是任何成员的最大对齐方式。 C编译器始终将您声明的变量放在满足“正确”对齐方式的地址上。 因此,如果ptr指向例如uint16_t变量,它将被对齐,您可以使用_amem2。 仅在访问例如通过I / O接收到的压缩字节数组或字符串中间的字节时,才需要使用_mem2。 回答2 许多计算机体系结构以每个几个字节的“字”存储内存。 例如,英特尔32位体系结构存储32位的字,每个字4个字节。 但是,内存是在单字节级别寻址的。 因此,地址可以是“对齐的”,这意味着它从字边界开始,或者是“未对齐的”,这意味着它不是。 在某些体系结构上,某些内存操作可能会变慢
  • 警告 C4799:函数没有 EMMS 指令(warning C4799: function has no EMMS instruction)
    问题 我正在尝试创建使用包含 C++ 代码和内联程序集的 dll 库的 C# 应用程序。 在函数 test_MMX 中,我想添加两个特定长度的数组。 extern "C" __declspec(dllexport) void __stdcall test_MMX(int *first_array,int *second_array,int length) { __asm { mov ecx,length; mov esi,first_array; shr ecx,1; mov edi,second_array; label: movq mm0,QWORD PTR[esi]; paddd mm0,QWORD PTR[edi]; add edi,8; movq QWORD PTR[esi],mm0; add esi,8; dec ecx; jnz label; } } 运行应用程序后,它显示此警告: 警告 C4799:函数“test_MMX”没有 EMMS 指令。 当我想以毫秒为单位测量运行此函数 C# 的时间时,它返回此值: -922337203685477而不是(例如0,0141 )... private Stopwatch time = new Stopwatch(); time.Reset(); time.Start(); test_MMX(first_array
  • 为什么他们在 x86-64 中使用数字作为寄存器名称?(Why did they use numbers for register names in x86-64?)
    问题 AFAIK x86-64 向从 Intel x86( rax 、 rcx等)派生的寄存器添加了许多通用寄存器,称为r8 - r15 。 他们为什么要这样命名新寄存器? 为什么不遵循现有的命名约定并将它们称为rfx 、 rgx ... ? 回答1 对 CPU 寄存器进行编号是常态,几乎所有处理器都会这样做。 然而,8086 处理器是古老的,他们在 1976 年的晶体管预算非常有限。实现一个只有 20,000 个有源晶体管的 16 位处理器是非常棒的。 他们减少的一种方法是为寄存器提供专用功能。 在这一点上,给它们命名而不是数字是有意义的,暗示它们的用法。 另一个影响是它旨在提供与 8080 处理器的兼容性级别,它还具有具有专用功能的命名寄存器。 完全相反的设计是摩托罗拉 68000,三年后采用更先进的工艺技术设计,使晶体管预算增加一倍。 一种非常正交的设计,(几乎)每个寄存器都可以在任何指令中自由使用。 并且与早期设计不兼容。 它有编号的寄存器(D0-D7 和 A0-A7)。 x86 架构的扩展再次使用编号寄存器,如 R8 到 R15、MM0 到 MM7、XMM0-15、YMM0-15 等。 回答2 为什么不遵循现有的命名约定 由于低 8 位名称不是任意序列或约定,因此它们的命名是出于特定目的。 r8-r15 没有任何特定目的,几乎没有隐含的用途或特殊性。 所有原始的 8