我正在为 AVR 平台开发一个定时循环,在其中对 ISR 内的单个字节进行倒计时。由于此任务是我的程序的主要功能,因此我想永久保留一个处理器寄存器,以便 ISR 在其通常的代码路径递减、与零比较和 时不必遇到内存障碍reti。
avr-libc 文档展示了如何将变量绑定到寄存器,我的工作没有出现问题。然而,由于这个变量是在主程序(用于启动计时器倒计时)和 ISR(用于实际计数和发出完成信号)之间共享的,因此还应该确保volatile编译器在优化它时不会做任何太聪明的事情。
在这种情况下(在整个整体构建中保留寄存器),这种组合volatile register在语义上对我来说是有意义的,因为“将此变量永久存储在 register 中rX,但不要优化检查,因为寄存器可能会在外部修改”。然而,GCC 不喜欢这样,并发出警告,表示它可能会继续优化变量访问。
GCC 中这种组合的错误历史表明编译器团队根本不愿意考虑我所描述的场景类型,并且认为提供它是没有意义的。我是否遗漏了该volatile register方法本身是一个坏主意的一些根本原因,或者这是一种具有语义意义但编译器团队对处理不感兴趣的情况?
我在 C 中有一个这样的函数(在伪代码中,删除不重要的部分):
int func(int s, int x, int *a, int *r) {
int i;
// do some stuff
for (i = 0; i < a_really_big_int; ++i) {
if (s)
r[i] = x ^ i;
else
r[i] = x ^ a[i];
// and maybe a couple other ways of computing r
// that are equally fast individually
}
// do some other stuff
}
Run Code Online (Sandbox Code Playgroud)
这段代码被调用得太多,以至于这个循环实际上是代码中的速度瓶颈。我想知道几件事:
由于 switchs是函数中的常量,好的编译器会优化循环,以便分支不会一直减慢速度吗?
如果没有,优化这段代码的好方法是什么?
====
编辑:这是一个包含更完整示例的更新:
int func(int s,
int start, int stop, …Run Code Online (Sandbox Code Playgroud) c optimization loops micro-optimization compiler-optimization
这本书说如下:
对于 Knights Landing,当数据起始地址位于 64 字节边界时,内存移动是最佳的。
Q1. 有没有办法在 C++ 代码中动态查询处理器,以了解n当前运行应用程序的处理器的最佳字节边界是什么?这样,代码就可以移植了。
书中进一步指出:
作为程序员,我们最终有两项工作:(1)对齐我们的数据;(2)确保编译器知道它是对齐的。
(假设对于下面的问题,我们知道处理器的最佳数据是从 64 字节边界开始。)
这个“数据”到底是什么?
假设我有一堂课:
class Class1_{
private:
int a;//4 bytes
double b;//8 bytes
std::vector<int> potentially_longish_vector_int;
std::vector<double> potentially_longish_vector_double;
double * potentially_longish_heap_array_double;
public:
//--stuff---//
double * return_heap_array_address() {return potentially_longish_heap_array_double;}
}
Run Code Online (Sandbox Code Playgroud)
假设我还有原型化的函数:
void func1(Class1_& obj_class1);
void func2(double* array);
Run Code Online (Sandbox Code Playgroud)
也就是说,通过引用func1接收一个对象,并调用为Class1_func2func2(obj_class1.return_heap_array_address());
为了与数据应该适当边界对齐的建议保持一致,obj_class1它本身应该是 64 字节边界对齐以实现 的有效运行func1()吗?应该potentially_longish_heap_array_double对齐 64 字节边界才能有效运行吗func2()?
对于作为 STL 容器的类的其他数据成员的对齐,此处的线程建议如何完成所需的对齐。
Q2。那么,对象本身以及其中的所有数据成员是否需要适当对齐?
c++ simd vectorization memory-alignment compiler-optimization
在尝试学习Coursera 上的课程时,我尝试为我的 CPU 优化示例 C++ 代码Intel i5-8259U,我相信它支持AVX2SIMD 指令集。现在,AVX2每个内核提供 16 个寄存器(称为YMM0, YMM1, ..., YMM15),宽度为 256 位,这意味着每个内核最多可以同时处理 4 个双精度浮点数。利用AVX2SIMD 指令应该可以优化我的代码,使其运行速度比标量指令快 4 倍。
在链接的课程中,您可以尝试在Intel Xeon Phi 7210 (Knights Landing)支持AVX512使用 512 位宽寄存器的处理器上运行相同的数值积分代码。这意味着我们应该期望双精度运算的速度提高 8 倍。实际上,讲师使用的代码获得了高达 14 倍的优化,几乎是 8 的 173%。额外的优化归功于 OpenMP。
为了在我的 CPU 上运行相同的代码,我唯一改变的是传递给英特尔编译器的优化标志:-xMIC-AVX512我使用了 ,而不是-xCORE-AVX2。我获得的加速仅为 2 倍,仅是由于 256 位寄存器上的 SIMD 矢量化而产生的预期加速的 50%。将此 50% 与英特尔至强融核处理器上获得的 173% 进行比较。
为什么我仅仅从 转到 就看到性能的巨大AVX512损失AVX2?当然,除了 …
我尝试了一下constexpr并发现了一些有趣的行为:
constexpr在函数前面添加使GCC能够尝试更努力地优化,从而导致完全优化函数并仅提供计算值。constexpr会导致错误,因为它内部使用了未标记的constexpr(特别是)函数/内部函数(编译器内置memcpy)。constexprconstexpr为什么会这样?
constexpr?memcpy constexpr(请参阅原始修订版中的第III.B节),但这被拒绝并更改,因为此类函数的编译器内置版本将实现相同的功能(请参阅最新修订版中的第 III.A节)。memcpy在constexpr函数/上下文中使用是错误的吗?(注:memcpy和__builtin_memcpy是等效的。)为了更容易理解,这里举个例子。(您甚至可以在此处的Compiler Explorer
中更轻松地查看其结果。)
注意:我无法想出一个简单的例子,其中简单地添加constexpr到函数中就可以帮助GCC优化器完全优化,否则它不会。但相信我,我有这样的例子,它们更复杂(遗憾的是闭源)。
#include <array>
#include <cstdint>
#include <cstring>
constexpr std::uint32_t extract(const std::uint8_t* data) noexcept
{ …Run Code Online (Sandbox Code Playgroud) 我在 C# 中使用 ArrayPool。我想创建自己的池,最大数组数量为 5,最大数组大小为 1050000。我使用了这个 ArrayPool.Create() 方法。我无法理解一件事 - 我试图在下面的代码片段中从池中租用 10 次,尽管我指定最大数组为 5,那么为什么它没有显示任何错误。另外,我指定最大长度为 1050000。那么我如何能够租用 4200000 的阵列而不出现任何错误?
byte[] buffer;
ArrayPool<byte> pool = ArrayPool<byte>.Create(1050000, 5);
for (int i = 0; i < 10; i++)
{
buffer = pool.Rent(4200000);
}
Run Code Online (Sandbox Code Playgroud) 我的代码有问题。如果我用 -O0 或 -Og 编译它,它似乎工作正常。
但是,如果我使用任何其他标志,如 -Os、-O1 等,则它不起作用。如何找到编译器优化的内容?
编译器 arm-none-eabi-g++ 8.3.1
源代码:https : //github.com/bielu000/stm32-libopencm3/tree/uart_not_working_version
我添加了到 repo 的链接,因为很难将整个代码放在这里。主要代码:src/app/src
我认为问题出在 server_run 函数中。
看看屏幕。
在左侧(它有效)->
优化:-O1,
属性((optimize("-O0"))) void server_run();
在右侧(它不起作用)->
优化:-O1,
void server_run();
我没有看到任何在优化(右)版本中获取缓冲区容量的调用。但为什么?
函数体
extern "C" {
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
}
#include <server.hpp>
#include <stdint.h>
#include <ring_buffer.hpp>
#include <Os.hpp>
#include <Timer.hpp>
#include <target.h>
static uint8_t w_buffer[1024]; // write buffer
static uint8_t r_buffer[1024]; // read buffer
utils::containers::RingBuffer write_rb{w_buffer, sizeof(w_buffer)};
utils::containers::RingBuffer read_rb{r_buffer, sizeof(r_buffer)};
static void sendData()
{ …Run Code Online (Sandbox Code Playgroud) 我用 C 编写了众所周知的交换函数,并使用 gcc S 观看了汇编输出,并再次做了同样的事情,但对 O2 进行了优化
差异非常大,因为与 20 行相比,我只看到了 5 行。
我的问题是,如果优化真的有帮助,为什么不一直使用它?为什么我们非优化编译代码?
给业内人士一个额外的问题,当您在测试后发布程序的最终版本时,您是否进行了优化编译?
我正在回复您的所有评论,请阅读。
有些程序已经使用 -O2 标志,如果我使用 -O3 标志,程序将使用 -O2 和 -O3 进行编译,如任务管理器或/proc/PID/cmdline.
例如,我使用的是用 Clang 和完整 LTO 构建的 Linux 内核。即使我在 dkms 配置中有这些行:
# /etc/dkms/framework.conf
export LLVM=1
export CC=clang
export CFLAGS="-O3 -march=native"
Run Code Online (Sandbox Code Playgroud)
现在,DKMS 模块使用-O2和-O3标志进行编译。在这种情况下,实际使用哪个标志?
这是我的测试代码:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
非常简单,我用它编译了它gcc -o helloworld helloworld.c(我也尝试过-g)。
但是,当我时objdump -tT helloworld,输出是:
helloworld: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 abi-note.c
000000000000039c l O .note.ABI-tag 0000000000000020 __abi_tag
0000000000000000 l df *ABS* 0000000000000000 init.c
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000001070 l F .text 0000000000000000 deregister_tm_clones
00000000000010a0 l F .text 0000000000000000 register_tm_clones
00000000000010e0 l F .text 0000000000000000 __do_global_dtors_aux
0000000000004030 l O .bss 0000000000000001 completed.0
0000000000003df0 l …Run Code Online (Sandbox Code Playgroud)