相关疑难解决方法(0)

在执行uop计数不是处理器宽度倍数的循环时性能是否会降低?

我想知道各种大小的循环如何在最近的x86处理器上执行,作为uop数的函数.

以下是彼得·科德斯(Peter Cordes)的一句话,他在另一个问题中提出了非多数的问题:

我还发现,如果循环不是4 uop的倍数,则循环缓冲区中的uop带宽不是每个循环的常数4.(即它是abc,abc,......;不是abca,bcab,......).遗憾的是,Agner Fog的microarch doc对循环缓冲区的这种限制并不清楚.

问题是关于循环是否需要是N uop的倍数才能以最大uop吞吐量执行,其中N是处理器的宽度.(即最近的英特尔处理器为4).在谈论"宽度"和计算微动时,有很多复杂因素,但我大多想忽略这些因素.特别是,假设没有微观或宏观融合.

Peter给出了以下一个循环,其中包含7个uop的循环:

一个7-uop循环将发出4 | 3 | 4 | 3 | ...的组我没有测试更大的循环(不适合循环缓冲区),看看是否有可能从下一个指令开始迭代发布在与其分支相同的组中,但我不假设.

更一般地说,声称是x在其体内具有uops 的循环的每次迭代将至少进行ceil(x / 4)迭代,而不是简单地迭代x / 4.

对于部分或全部最新的x86兼容处理器,这是真的吗?

performance x86 assembly cpu-architecture micro-optimization

20
推荐指数
2
解决办法
2048
查看次数

更快地检查C中的全零缓冲区的方法?

我正在寻找一种更快的方法来完成这个:

int is_empty(char * buf, int size) 
{
    int i;
    for(i = 0; i < size; i++) {
        if(buf[i] != 0) return 0;
    }
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

我意识到我正在寻找一种微观优化,除非在极端情况下,但我知道存在更快的方法,我很好奇它是什么.

c optimization performance buffer

16
推荐指数
8
解决办法
1万
查看次数

是否更快地访问静态或动态分配的内存?

在C中有两种分配全局数组的方法:

  1. 静态

    char data[65536];
    
    Run Code Online (Sandbox Code Playgroud)
  2. 动态

    char *data;
    …
    data = (char*)malloc(65536);  /* or whatever size */
    
    Run Code Online (Sandbox Code Playgroud)

问题是,哪种方法有更好的表现?多少钱?

理解它,第一种方法应该更快.

因为使用第二种方法,要访问数组,每次访问时都必须取消引用元素的地址,如下所示:

  1. 读取data包含指向数组开头的指针的变量
  2. 计算特定元素的偏移量
  3. 访问元素

使用第一种方法,编译器将data变量的地址硬编码到代码中,跳过第一步,因此我们有:

  1. 从编译时定义的固定地址计算特定元素的偏移量
  2. 访问数组的元素

每次存储器访问相当于大约40个CPU时钟周期,因此,使用动态分配,特别是对于不频繁的读取,与静态分配相比可以显着降低性能,因为data可以通过一些更频繁访问的变量从缓存中清除变量.相反,解除引用静态分配的全局变量的成本是0,因为它的地址已经在代码中进行了硬编码.

它是否正确?

c arrays performance

16
推荐指数
2
解决办法
3253
查看次数

在某些CPU的紧密循环中出现ADC/SBB和INC/DEC问题

我在Delphi中编写一个简单的BigInteger类型.它主要由TLimb的动态数组组成,其中TLimb是32位无符号整数,32位大小字段,它还保存BigInteger的符号位.

要添加两个BigIntegers,我创建一个适当大小的新BigInteger然后,在一些簿记之后,调用以下过程,将三个指针传递给左右操作数和结果的数组的相应开始,以及左右肢的数量分别为.

普通代码:

class procedure BigInteger.PlainAdd(Left, Right, Result: PLimb; LSize, RSize: Integer); 
asm
// EAX = Left, EDX = Right, ECX = Result
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        MOV     ESI,EAX                 // Left
        MOV     EDI,EDX                 // Right
        MOV     EBX,ECX                 // Result
        MOV     ECX,RSize               // Number of limbs at Left
        MOV     EDX,LSize               // Number of limbs at Right
        CMP     EDX,ECX
        JAE     @SkipSwap
        XCHG    ECX,EDX                 // Left and LSize should be largest
        XCHG    ESI,EDI                 // so swap
@SkipSwap:
        SUB     EDX,ECX                 // …
Run Code Online (Sandbox Code Playgroud)

delphi x86 assembly

15
推荐指数
2
解决办法
919
查看次数

我对AoS vs SoA的理解是对吗?

我最近一直在阅读AoS vs SoA结构设计和面向数据的设计.很难找到关于这两者的信息,而且我发现的东西似乎比我拥有更多的处理器功能.也就是说,我对前一个主题的理解特别导致了一些我认为应该能够理解答案的问题.

首先,为了确保我的理解不是基于错误的前提,我对AoS vs SoA的功能和利弊的理解,应用于具有'Name'和'Age'字段的'Person'记录的集合与他们相关:

阵列的结构

  • 将数据存储为由多个数组组成的单个结构,例如,将People字段Names作为字符串Ages数组和整数数组作为对象.
  • 信息,说,第三人的名单将通过类似给予People.Names[2]People.Ages[2]
  • 优点:
    • 当仅处理来自许多"人"记录的一些数据时,只需要从内存加载该数据.
    • 所述数据以同类方式存储,允许在大多数此类情况下通过SIMD指令更好地使用高速缓存.
  • 缺点: - 当需要一次访问多个字段时,上述优点就会消失. - 访问一个或几个对象的所有数据变得效率较低. - 大多数编程语言需要更冗长,更难以读/写的代码,因为没有明确的"Person"结构.

结构数组

  • 将数据存储为多个结构,每个结构都有一整套字段,它们本身存储在所有这些结构的数组中,例如对象People数组Person,它们具有Name字符串字段和Age整数字段.
  • 对于第三人的信息会被像被赋予People[2].NamePeople[2].Age
  • 优点:
    • 代码围绕一个更简单的心理模型构建,间接被抽象掉.
    • 单个记录易于访问和使用.
    • Person结构的存在使得在大多数编程语言中编写代码变得更加简单.
  • 缺点:
    • 当处理来自大量记录的一些数据时,需要将整组结构加载到包括不相关数据的存储器中.
    • 结构阵列不是均匀的,在这种情况下限制了SIMD指令可以提供的优点.

它的长短似乎是,假设为了论证,你的性能瓶颈是数据访问和编码的简易性是无关紧要的,如果你几乎完全需要一次访问大量的单个字段数据SoA可能更具性能,而如果您经常需要从同一个对象访问多个字段或处理单个对象而不是一次处理多个字段,AoS将更具性能.

也就是说,我一直在阅读的一些内容似乎让图片变得混乱.首先,多个消息来源已经声明SoA需要索引寻址,据称这是低效的.我无法理解这一点,也无法找到任何解释.在我看来,AoS和SoA需要完全相同的操作来访问任何特定的数据,尽管顺序不同,除了SoA需要一个额外的指针(可能多于一个,取决于所使用的结构类型).稍微简化一下,为了在AoS下面的上面例子中得到第五个人的年龄,你首先得到指向数组的指针,向它添加4,在数组的那个元素处获取结构指针,添加一个大小字符串指向它,因为age是第二个字段,然后访问该指针处的整数.在SoA下,您将获得指向结构的指针并向其添加字符串数组指针的大小以获取年龄列表,然后获取指向存储在那里的整数列表的指针并向其添加4,然后获取整数存储在那里.

其次,我不清楚SoA的好处在多大程度上取决于特定的CPU架构.一方面,我对上述优点的理解并不依赖于任何特定的体系结构,除了SIMD指令在某些情况下可以提供AoS下无法提供的额外好处.另一方面,我看到声称可以限制SoA的优势,具体取决于特定SIMD架构中可用的通道数量.同样,这似乎只会影响SIMD指令可以提供的更多通用缓存优势的额外好处.

最后,我已经看到SoA在遍历数据时需要更多缓存方式的说法.我不完全确定缓存方式是什么或者什么,如果有的话,特别是'遍历'数据.我最好的猜测是"缓存方式"指的是关联缓存中潜在冲突的数量或与之相关,并且它与上面提到的第二个Con相关.

memory caching sse simd data-oriented-design

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

为什么编译器在这里错过矢量化?

考虑以下valarray类:

#include <stdlib.h>

struct va
{
    void add1(const va& other);
    void add2(const va& other);

    size_t* data;
    size_t  size;
};

void va::add1(const va& other) {
    for (size_t i = 0; i < size; ++i) {
        data[i] += other.data[i];
    }
}

void va::add2(const va& other){
    for (size_t i = 0, s = size; i < s; ++i) {
        data[i] += other.data[i];
    }
}
Run Code Online (Sandbox Code Playgroud)

add2函数针对不同的编译器(MSVC、Clang、GCC、ICC)进行了向量化,而add1并非如此。参见https://godbolt.org/z/c61qvrrbv

这是通过潜在的别名来解释的:编译器无法证明所指向的元素之一data不是其size本身。

data然而,和指向的元素也可能存在重叠other.data。对于 MSVC,这些元素和指针本身可能存在别名,因为它没有利用严格别名规则。这适用于add1 …

c++ vectorization strict-aliasing compiler-optimization auto-vectorization

14
推荐指数
1
解决办法
456
查看次数

使用CMP reg测试寄存器是否为零,0与OR reg,reg?

使用以下代码是否存在任何执行速度差异:

cmp al, 0
je done
Run Code Online (Sandbox Code Playgroud)

以下内容:

or al, al
jz done
Run Code Online (Sandbox Code Playgroud)

我知道JE和JZ指令是相同的,并且使用OR可以提供一个字节的大小改进.但是,我也关心代码速度.逻辑运算符似乎比SUB或CMP更快,但我只是想确定.这可能是规模和速度之间的权衡,或双赢(当然代码将更加不透明).

optimization x86 assembly micro-optimization

13
推荐指数
2
解决办法
3755
查看次数

了解lfence对具有两个长依赖链的循环的影响,以增加长度

我正在玩这个答案的代码,稍微修改一下:

BITS 64

GLOBAL _start

SECTION .text

_start:
 mov ecx, 1000000

.loop:

 ;T is a symbol defined with the CLI (-DT=...)

 TIMES T imul eax, eax
 lfence
 TIMES T imul edx, edx


 dec ecx
jnz .loop

 mov eax, 60           ;sys_exit
 xor edi, edi
 syscall
Run Code Online (Sandbox Code Playgroud)

没有lfence我,我得到的结果与答案中的静态分析一致.

当我介绍一个单一 lfence我期望的CPU执行imul edx, edx的序列的第k个平行于迭代imul eax, eax的下一个(的序列K + 1个)迭代.
像这样的东西(调用一个imul eax, eax序列和dimul edx, edx一个): …

performance x86 assembly cpu-architecture perf

13
推荐指数
2
解决办法
472
查看次数

在大数组中有效地找到最低有效设置位?

我有一个巨大的内存块(位向量),在一个内存页中大小为N位,考虑N平均为 5000,即 5k 位来存储一些标志信息。
在某个时间点(超级频繁 - 关键),我需要在整个大位向量中找到第一个位集。现在我每 64 个字都这样做,即在 ) 的帮助下__builtin_ctzll。但是当N增长并且搜索算法无法改进时,可以通过扩展内存访问宽度来扩展此搜索。这是几句话的主要问题

有一条被调用的汇编指令BSF 给出了最高设置位(GCC's __builtin_ctzll())的位置。因此,在 arch 中,我可以在 64 位字中廉价地找到最高位。

但是通过内存宽度进行缩放呢?
例如,有没有办法用 128 / 256 / 512 位寄存器有效地做到这一点?
基本上我对一些 C API 函数来实现这个感兴趣,但也想知道这个方法是基于什么的。

UPD:至于 CPU,我对这种优化感兴趣,以支持以下 CPU 阵容:
英特尔至强 E3-12XX、英特尔至强 E5-22XX/26XX/E56XX、英特尔酷睿 i3-5XX/4XXX/8XXX、英特尔酷睿 i5- 7XX、英特尔赛扬 G18XX/G49XX(英特尔凌动 N2600、英特尔赛扬 N2807、Cortex-A53/72 可选)

PS在最终位扫描之前提到的算法中,我需要将k(平均 20-40)个N位向量与 CPU AND相加(AND 结果只是位扫描的准备阶段)。这也适用于内存宽度缩放(即比每 64 位字 AND 更有效)

另请阅读:查找第一组

c assembly bit-manipulation x86-64 avx

13
推荐指数
2
解决办法
465
查看次数

为什么天真的C++矩阵乘法比BLAS慢100倍?

我正在研究大型矩阵乘法并运行以下实验来形成基线测试:

  1. 从std normal(0 mean,1 stddev)随机生成两个4096x4096矩阵X,Y.
  2. Z = X*Y.
  3. Z的Sum元素(以确保它们被访问)和输出.

这是天真的C++实现:

#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    constexpr size_t dim = 4096;

    float* x = new float[dim*dim];
    float* y = new float[dim*dim];
    float* z = new float[dim*dim];

    random_device rd;
    mt19937 gen(rd());
    normal_distribution<float> dist(0, 1);

    for (size_t i = 0; i < dim*dim; i++)
    {
        x[i] = dist(gen);
        y[i] = dist(gen);
    }

    for (size_t row = 0; row < dim; row++)
        for (size_t col = 0; col < …
Run Code Online (Sandbox Code Playgroud)

c++ linux matlab matrix-multiplication c++11

12
推荐指数
2
解决办法
3347
查看次数