在最近的CPU上(至少在过去十年左右),除了各种可配置的性能计数器之外,英特尔还提供了三个固定功能硬件性能计数器.三个固定柜台是:
INST_RETIRED.ANY
CPU_CLK_UNHALTED.THREAD
CPU_CLK_UNHALTED.REF_TSC
Run Code Online (Sandbox Code Playgroud)
第一个计算退役指令,第二个计算实际周期,最后一个是我们感兴趣的."英特尔软件开发人员手册"第3卷的描述如下:
当核心未处于暂停状态而不处于TM停止时钟状态时,此事件计算TSC速率下的参考周期数.核心在运行HLT指令或MWAIT指令时进入暂停状态.此事件不受核心频率变化(例如,P状态)的影响,但计数与时间戳计数器的频率相同.当核心未处于暂停状态而不处于TM stopclock状态时,此事件可以估计经过的时间.
因此,对于CPU绑定循环,我希望该值与从中读取的自由运行TSC值相同rdstc,因为它们应该仅针对暂停的循环指令或"TM stopclock state"是什么发散.
我使用以下循环测试它(整个独立演示在github上可用):
for (int i = 0; i < 100; i++) {
PFC_CNT cnt[7] = {};
int64_t start = nanos();
PFCSTART(cnt);
int64_t tsc =__rdtsc();
busy_loop(CALIBRATION_LOOPS);
PFCEND(cnt);
int64_t tsc_delta = __rdtsc() - tsc;
int64_t nanos_delta = nanos() - start;
printf(CPU_W "d" REF_W ".2f" TSC_W ".2f" MHZ_W ".2f" RAT_W ".6f\n",
sched_getcpu(),
1000.0 * cnt[PFC_FIXEDCNT_CPU_CLK_REF_TSC] / nanos_delta,
1000.0 * tsc_delta / nanos_delta,
1000.0 * CALIBRATION_LOOPS / nanos_delta,
1.0 * cnt[PFC_FIXEDCNT_CPU_CLK_REF_TSC]/tsc_delta); …Run Code Online (Sandbox Code Playgroud) 我没有找到运算符%的文档,因为它在Python中的字符串上使用.有人知道文档的位置吗?
我想实现的无符号一整数除法通过两个任意功率,围捕,高效.所以我想要的,数学上是ceiling(p/q)0.在C中,不利用受限域的strawman实现q可能类似于以下函数1:
/** q must be a power of 2, although this version works for any q */
uint64_t divide(uint64_t p, uint64_t q) {
uint64_t res = p / q;
return p % q == 0 ? res : res + 1;
}
Run Code Online (Sandbox Code Playgroud)
...当然,我实际上并不想在机器级别使用除法或mod,因为即使在现代硬件上也需要很多周期.我正在寻找使用轮班和/或其他一些廉价操作的力量减少 - 利用q2的力量这一事实.
You can assume we have an efficient lg(unsigned int x) function, which returns the base-2 log of x …
我看到一个简单的存储循环出乎意料地表现不佳,这个存储循环有两个存储:一个具有16字节的正向步长,另一个总是位于同一位置1,如下所示:
volatile uint32_t value;
void weirdo_cpp(size_t iters, uint32_t* output) {
uint32_t x = value;
uint32_t *rdx = output;
volatile uint32_t *rsi = output;
do {
*rdx = x;
*rsi = x;
rdx += 4; // 16 byte stride
} while (--iters > 0);
}
Run Code Online (Sandbox Code Playgroud)
在汇编这个循环可能3看起来像:
weirdo_cpp:
...
align 16
.top:
mov [rdx], eax ; stride 16
mov [rsi], eax ; never changes
add rdx, 16
dec rdi
jne .top
ret
Run Code Online (Sandbox Code Playgroud)
当访问的存储区域在L2中时,我希望每次迭代运行少于3个周期.第二个商店只是一直在同一个位置,应该添加一个周期.第一个商店意味着从L2引入一条线,因此每4次迭代也会驱逐一条线.我不确定你如何评估L2成本,但即使你保守估计L1只能在每个周期中执行以下操作之一:(a)提交商店或(b)从L2接收一行或(c)将一条线驱逐到L2,对于stride-16商店流,你会得到1 + 0.25 + …
考虑以下结构:
struct s {
int a, b;
};
Run Code Online (Sandbox Code Playgroud)
通常为1,此结构的大小为 8,对齐方式为 4。
如果我们创建两个struct s对象(更准确地说,我们将两个这样的对象写入分配的存储空间),并且第二个对象与第一个对象重叠会怎样?
char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4
// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};
printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);
Run Code Online (Sandbox Code Playgroud)
这个程序的未定义行为有什么问题吗?如果是这样,它在哪里变得未定义?如果不是UB,是否保证始终打印以下内容:
o2.a=3
o2.b=4
o1.a=1
o1.b=3
Run Code Online (Sandbox Code Playgroud)
特别是,我想知道o1 …
根据我与几个不同的Oracle和OpenJDK的实现测试,似乎Arrays.equals(char[], char[])是某种大约快8倍比其他类型的所有其他变体.
如果您的应用程序的性能在很大程度上相关0与平等比较阵列,这意味着你很可能要强制所有的数据到char[],只是为了得到这个神奇的性能提升.
最近我写了一些高性能代码,用于Arrays.equals(...)比较用于索引到结构的键.密钥可能很长,并且通常仅在后面的字节中有所不同,因此这种方法的性能非常重要.
在一个点我用类型的键char[],但作为推广服务的一部分,并且避免从底层的来源一些副本byte[]和ByteBuffer,我改变这对byte[].突然2,许多基本操作的表现下降了约3倍.我将其追溯到上述事实:Arrays.equals(char[], char[])似乎在所有其他Arrays.equals()版本中享有特殊状态,包括在short[]语义上相同的一个版本(并且可以使用相同的底层代码实现,因为签名不会影响equals的行为).
所以我写了一个JMH基准测试的所有原始变种Arrays.equals(...)1和char[]变异击碎所有其它的,如上图所示.
现在,~8x变种的这种优势并没有扩大到更小或更大的阵列 - 但它仍然更快.
对于小型阵列,似乎常数因素开始占主导地位,对于较大的阵列,L2/L3或主内存带宽开始起作用(您可以在前面的图中清楚地看到后者的影响,其中int[]尤其是long[]阵列开始降低大尺寸的性能).下面是相同的测试,但是有一个较小的小数组和较大的大数组:
在这里,char[]仍然踢屁股,只是没有像以前那么多.小数组(仅16个元素)的每个元素时间大约是标准时间的两倍,可能是由于函数开销:在大约0.5 ns /元素时,char[]整个调用的变量仍然只需要大约7.2纳秒,或大约19纳秒在我的机器上循环 - 所以少量的方法开销会大量削减运行时间(同样,基准开销本身就是几个循环).
在大端,缓存和/或内存带宽是一个驱动因素 - long[]变体几乎是变体的2 倍int[].的short[],尤其是byte[]变种都不是很有效的(他们的工作设置仍然适用于L3在我的机器).
char[]与其他所有应用程序之间的差异非常大,以至于对于依赖于阵列比较的应用程序(对于某些特定域而言,这实际上并不常见),尝试将所有数据char[]用于利用是值得的.呵呵.
是什么赋予了?是否char得到特殊待遇,因为它是某些String …
现代CPU具有广泛的流水线操作,也就是说,它们在实际执行指令之前很久就会加载必要的指令和数据.
有时,加载到管道中的数据会失效,必须清除管道并重新加载新数据.重新填充管道所需的时间可能相当长,并导致性能下降.
如果我在C中调用一个函数指针,那么管道是否足够智能以实现管道中的指针是一个函数指针,并且它应该跟随该指针用于下一个指令?或者是否有一个函数指针导致管道清除并降低性能?
我在C中工作,但我想这在C++中更为重要,因为许多函数调用都是通过v-tables进行的.
要成为函数调用的真正性能,您调用的函数必须非常简短.如果您通过测量代码来观察这一点,那么您最终应该重新审视您的设计以允许内联调用
不幸的是,这可能是我陷入的陷阱.
出于性能原因,我编写了小而快的目标函数.
但是它被函数指针引用,因此它可以很容易地被其他函数替换(只需使指针引用一个不同的函数!).因为我通过函数指针引用它,所以我认为它不能内联.
所以,我有一个非常简短,没有内联的功能.
据我所知,mfence硬件内存屏障asm volatile ("" : : : "memory")是一个编译器障碍.但是,可以asm volatile ("" : : : "memory")用来代替mfence.
我迷惑的原因是这个链接
当我们在c ++中为类创建一个成员函数时,它有一个隐式的额外参数,它是一个指向调用对象的指针 - 被称为this.
对于任何函数都是如此,即使它不使用this指针也是如此.例如,给定班级
class foo
{
private:
int bar;
public:
int get_one()
{
return 1; // Not using `this`
}
int get_bar()
{
return this->bar; // Using `this`
}
}
Run Code Online (Sandbox Code Playgroud)
两个函数(get_one和get_bar)都将this作为隐式参数,即使其中只有一个实际使用它吗?
这样做似乎有点浪费.
注意:我理解正确的做法是做get_one()静态,答案可能取决于实现,但我只是好奇.
如果我替换所有operator new我可以签名的签名,至少在我测试的实现上,我看到标准容器调用我的替换版本来分配内存.
这是否由标准保证?也就是说,实现使用优化版本是不合法的,该版本没有将我的替换函数称为标准容器下的内存?