典型的 makefile 通常使用内置变量CFLAGS、等CXXFLAGS1来设置传递给 C、C++ 或其他编译器/工具的标志。原则上,有时这甚至可以让您完全避免编写编译配方,因为各种内置规则使用这些标志。CPPFLAGS
一般来说,makefile 可能会向所需的FLAGS变量中添加内容添加内容,例如包含目录、指示要使用哪种语言标准的参数等。这些变量还可能包括“可选”或“默认”参数,例如优化级别、警告级别和其他可以有效更改或删除的设置。
由于CFLAGS和 字段是“众所周知的”变量,因此它们显然也是最终用户的配置点。例如,如果默认情况下项目在没有调试信息的情况下进行编译,则预期CFLAGS=-g在make命令行上会导致-g添加到 $(CC) 编译器命令行,从而导致生成调试信息。同样,对于最终用户可能想要控制的其他选项,例如优化级别、-marchgcc 的设置等。
然而,这两种用途对我来说似乎不兼容。如果用户覆盖,$(CFLAGS)他们将删除如上所述的任何内部“必需”标志,并且项目可能无法编译或可能编译不正确。
有处理这个问题的最佳实践吗?对于“单值”变量来说,实际上不会出现同样的问题,$(CC)因为它们通常只有一个值:在本例中,是要使用的 C 编译器。如果用户覆盖它,您就使用它们的值。原则上,诸如此类$(CFLAGS)的内容是一个值列表,其中一些是内部的,不应被覆盖,而另一些则是用户可能想要覆盖的。
直观上,解决方案似乎是$(CFLAGS)在 makefile 中将 and Friends 保留为空且未使用,更喜欢CFLAGS_INTERNAL在 makefile 中指定参数,然后将两者都放在命令行上。然而,我很好奇是否有最佳实践,或者我是否遗漏了一些明显的东西。
1对于这个问题的其余部分,我通常会简单地引用它,$(CFLAGS)并理解这只是整个众所周知的编译器标志变量系列的方便代表,例如$(CPPFLAGS),$(CXXFLAGS)等等。
在最近的英特尔ISA文档中,该lfence指令被定义为序列化指令流(防止指令流无序执行).特别是,该指令的描述包括以下行:
具体来说,LFENCE不会执行,直到所有先前的指令在本地完成,并且在LFENCE完成之前没有后续指令开始执行.
请注意,这适用于所有的指令,不只是内存加载指令,使得lfence 更多的不仅仅是一个存储排序防护.
虽然这现在出现在ISA文档中,但不清楚它是否是"架构",即所有x86实现都遵守,或者它是否特定于Intel.特别是AMD处理器是否也将lfence序列化为指令流?
多年来,x86 CPU 都支持该rdtsc指令,该指令读取当前 CPU 的“时间戳计数器”。这个计数器的确切定义随着时间的推移而改变,但在最近的 CPU 上,它是一个相对于挂钟时间以固定频率递增的计数器,因此它作为快速、准确时钟或测量时间的构建块非常有用由小段代码获取。
关于rdtsc指令的一个重要事实没有以任何特殊方式与周围的代码一起排序。像大多数指令一样,它可以相对于与它没有依赖关系的其他指令自由地重新排序。这实际上是“正常的”,对于大多数指令,它只是一种使 CPU 更快的几乎不可见的方式(这只是一种长篇大论的无序执行方式)。
因为rdtsc它很重要,因为这意味着您可能没有为您期望的代码计时。例如,给定以下序列1:
rdtsc
mov ecx, eax
mov rdi, [rdi]
mov rdi, [rdi]
rdtsc
Run Code Online (Sandbox Code Playgroud)
您可能希望rdtsc测量追逐加载负载的两个指针的延迟mov rdi, [rdi]。然而,在实践中,即使这两个加载都需要查看时间(如果它们在缓存中丢失,则为 100 秒),您将获得相当小的读取值rdtsc。问题是第二个rdtsc不等待加载完成,它只是乱序执行,所以你没有按你认为的时间间隔计时。也许这两rdtsc条指令实际上甚至在第一次加载开始之前就执行了,这取决于rdi在此示例之前的代码中是如何计算的。
到目前为止,这听起来更像是对一个没人问的问题的回答,而不是一个真正的问题,但我已经到了那里。
您有两个基本用例rdtsc:
作为一种精确的计时机制,例如,在微基准测试中。在这种情况下,您通常会rdtsc根据lfence说明防止重新订购。对于上面的示例,您可能会执行以下操作:
lfence
rdtsc
lfence
mov ecx, eax
...
lfence
rdtsc
Run Code Online (Sandbox Code Playgroud)
确保定时指令 ( ...) 不会逃逸到定时区域之外,并确保来自时间区域内的指令不会进入(可能问题不大,但它们可能会与您想要的代码竞争资源测量)。
多年后,英特尔看不起我们这些可怜的程序员,并提出了一条新指令:rdtscp. 就像rdtsc它返回时间戳计数器的读数一样,这家伙做了更多的事情:它使用时间戳读数原子地读取特定于内核的 MSR …
写合并缓冲区一直是Intel CPU的功能,至少可以追溯到Pentium 4甚至更早。基本思想是这些高速缓存行大小的缓冲区将写操作收集到同一高速缓存行中,因此可以将它们作为一个单元进行处理。作为它们对软件性能的影响的一个示例,如果您不编写完整的缓存行,则可能会遇到性能下降的情况。
例如,在《Intel 64和IA-32体系结构优化参考手册》中,“ 3.6.10写合并”部分以以下说明(加了重点)开头:
写合并(WC)通过两种方式提高性能:
•在对第一级缓存的写入未命中时,它允许在从缓存/内存层次结构的更深层读取所有权(RFO)之前对该同一个缓存行进行多个存储。然后读取剩余的行,将尚未写入的字节与返回行中的未修改字节合并。
•写合并允许将多个写组合在一起,并作为一个单元在高速缓存层次结构中进一步写出。这样可以节省端口和总线流量。节省流量对于避免部分写入未缓存的内存特别重要。
有六个写合并缓冲区(在奔腾4和Intel Xeon处理器上,CPUID签名的族编码为15,模型编码为3;有8个写合并缓冲区)。这些缓冲区中的两个可以写出更高的缓存级别,并释放出来以供其他写未命中。保证只有四个写合并缓冲区可同时使用。写合并适用于存储器类型WC;它不适用于内存类型UC。
Intel Core Duo和Intel Core Solo处理器的每个处理器内核中都有六个写合并缓冲区。基于英特尔酷睿微体系结构的处理器在每个内核中都有八个写合并缓冲区。从英特尔微体系结构代码名称Nehalem开始,有10个缓冲区可用于写合并。
写合并缓冲区用于所有存储器类型的存储。它们对于写入未缓存的内存特别重要...
我的问题是,在使用普通存储区(非临时存储区以外的其他存储区,即您所使用的存储区)时,写合并是否适用于WB内存区域(即您在用户程序中使用99.99%的时间的“普通”内存)正在使用99.99%的时间)。
上面的文字很难准确解释,因为自Core Duo时代以来没有更新过。您有说写梳理的部分“适用于WC存储器,但不适用于UC”,但是当然不包括所有其他类型,例如WB。后来,您发现“ [WC对于写入未缓存的内存特别重要”,这似乎与“不适用于UC部分”相矛盾。
那么,现代英特尔芯片上用于正常存储到WB存储器的写合并缓冲区是否有效?
我想用一些样板代码包装一些代码行,所以我应该通过将多行代码作为宏参数传递来执行它,如下所示:
#define safeRun(x) if (ValidationOK()) {x}
int main(int argc, char **argv) {
safeRun(
foo();
bar();
)
}
Run Code Online (Sandbox Code Playgroud)
非常感谢.
我有一个缓慢的CPU密集型操作:doWork(int x)使用具有不同值的单个整数参数调用,如下所示:
static String doWork(int x) {
// do work that depends on i, takes ~60 seconds
...
}
public static void main(String args[]) {
for (int i = 1; i < 100; i++) {
System.println(doWork(i));
}
}
Run Code Online (Sandbox Code Playgroud)
每次doWork()调用完成后,结果都会输出到控制台.我想并行化这个 - 所有的doWork()调用都是独立的,不会改变任何共享状态.现在,我可以做到这一点的老办法,瞎搞与ExecutorSevice和Future.get()等,但我想用流做更干净1.
所以像这样的东西似乎几乎可以工作:
public static void main(String args[]) {
IntStream.rangeClosed(1, 100).parallel()
.forEach(i -> System.out.println(doWork(i)));
}
Run Code Online (Sandbox Code Playgroud)
...但问题是我想保留控制台上的输出顺序(doWork(1)应该先行,依此类推).我无法使用forEachOrdered()因为序列化整个操作:只使用一个线程.问题的根源在于forEachOrdered提供了太强有力的保证:一次在一个元素上顺序调用consumer方法.我希望并行调用消费者,但输出是有序的.
所以我应该看一下map -> collect类型习惯用法,我将每次doWork()调用的输出收集到一个字符串中并打印一次: …
想象一下,我有一个X宏,用于定义如下内容的项目列表:
#define X_MACRO(FN) \
FN(foo) \
FN(bar) \
FN(zip)
Run Code Online (Sandbox Code Playgroud)
这很好用,我可以调用它来生成为每个元素模板化的相同代码,例如:
#define xstr(s) str(s)
#define str(s) #s
#define PRINT_X(E) void print_ ## E () { std::cout << str(E); };
X_MACRO(PRINT_X)
Run Code Online (Sandbox Code Playgroud)
这会void print_foo() { std::cout << "foo"; };为每个X_MACRO元素生成类似的函数.到现在为止还挺好.
但是,现在,我希望X宏元素列表以预处理器宏为条件.例如,zip元素应仅包含在X宏中(如果USE_ZIP已定义).当然,我不能把#ifdefX宏放在里面,比如:
#define X_MACRO(FN) \
FN(foo) \
FN(bar) \
#ifdef USE_ZIP
FN(zip)
#endif
Run Code Online (Sandbox Code Playgroud)
我可以改为将列表写两次,一次使用zip,一次不使用,基于USE_ZIP类似:
#ifdef USE_ZIP
#define X_MACRO(FN) \
FN(foo) \
FN(bar) \
FN(zip)
#else
#define X_MACRO(FN) \
FN(foo) \
FN(bar) …Run Code Online (Sandbox Code Playgroud) 考虑一下 C++ 中的这个函数:
void foo(uint32_t *a1, uint32_t *a2, uint32_t *b1, uint32_t *b2, uint32_t *o) {
while (b1 != b2) {
// assert(0 <= *b1 && *b1 < a2 - a1)
*o++ = a1[*b1++];
}
}
Run Code Online (Sandbox Code Playgroud)
其目的应该足够明确。不幸的是,b1包含随机数据并垃圾缓存,成为foo我的程序的瓶颈。无论如何我可以优化它吗?
这是一个 SSCCE,应该类似于我的实际代码:
#include <iostream>
#include <chrono>
#include <algorithm>
#include <numeric>
namespace {
void foo(uint32_t *a1, uint32_t *a2, uint32_t *b1, uint32_t *b2, uint32_t *o) {
while (b1 != b2) {
// assert(0 <= *b1 && *b1 < a2 - …Run Code Online (Sandbox Code Playgroud) 据我所知,memcpy进入未初始化的存储不能安全地用于创建源对象的副本。
但是,在去年 open-std WG21“ub”列表上的这个线程中,参与者提到了新的 memcpy 生命周期启动规则:
这似乎没有上升到错误报告的级别,但它可能与新的 memcpy 生命周期启动规则有关。他们会为源字节和目标字节分配一些静态类型吗?
Based on the context of the question and small amount of type-erasure example code, it appears that it may be related to creating new objects in aligned_storage via memcpy.
Search as I might, I can't find a reference to the new rules. I'm particularly curious if they only apply to replacing the contents of an already created object, or if they change …
执行 a 所需的时间是否在rdmsr很大程度上取决于寄存器读取?