我目前正試圖改善自定義「僞」棧,這樣使用(完整代碼在這篇文章的末尾提供)的性能:定期由於採集釋放內存排序而錯過了優化機會或所需行爲?
void test() {
theStack.stackFrames[1] = StackFrame{ "someFunction", 30 }; // A
theStack.stackTop.store(1, std::memory_order_seq_cst); // B
someFunction(); // C
theStack.stackTop.store(0, std::memory_order_seq_cst); // D
theStack.stackFrames[1] = StackFrame{ "someOtherFunction", 35 }; // E
theStack.stackTop.store(1, std::memory_order_seq_cst); // F
someOtherFunction(); // G
theStack.stackTop.store(0, std::memory_order_seq_cst); // H
}
採樣器線程暫停目標線程並讀取stackTop
和stackFrames
陣列。
我最大的性能問題是順序一致的商店到stackTop
,所以我試圖找出我是否可以將它們更改爲發佈商店。
中心要求是:當採樣器線程掛起目標線程並讀取stackTop == 1
時,stackFrames[1]
中的信息需要完全呈現並保持一致。這意味着:
- 當觀察到B時,還必須觀察到A. (「不要把堆棧幀到位之前增加
stackTop
」) - 當觀察到E,d也必須遵守。 (「到位時,把下一幀的信息,先前的堆棧幀必須已經退出。」)
我的理解是,用釋放獲取內存排序爲stackTop
保證的第一要求,而不是第二。更具體地說:
- 在程序順序中的
stackTop
版本存儲之前沒有寫入可以重新排序以在其之後發生。
不過,聲明沒有作出有關出現後釋放,商店stackTop
在程序順序寫入。因此,我的理解是E可以在觀察到D之前觀察到。它是否正確?
但是,如果是這樣的話,以後就不會編譯器能夠重新安排我的程序是這樣的:
void test() {
theStack.stackFrames[1] = StackFrame{ "someFunction", 30 }; // A
theStack.stackTop.store(1, std::memory_order_release); // B
someFunction(); // C
// switched D and E:
theStack.stackFrames[1] = StackFrame{ "someOtherFunction", 35 }; // E
theStack.stackTop.store(0, std::memory_order_release); // D
theStack.stackTop.store(1, std::memory_order_release); // F
someOtherFunction(); // G
theStack.stackTop.store(0, std::memory_order_release); // H
}
...然後結合d和F,優化掉零店?
因爲這不是我所看到的,如果我編譯使用在MacOS系統鐺上面的程序:
$ clang++ -c main.cpp -std=c++11 -O3 && objdump -d main.o
main.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
__Z4testv:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 48 8d 05 5d 00 00 00 leaq 93(%rip), %rax
b: 48 89 05 10 00 00 00 movq %rax, 16(%rip)
12: c7 05 14 00 00 00 1e 00 00 00 movl $30, 20(%rip)
1c: c7 05 1c 00 00 00 01 00 00 00 movl $1, 28(%rip)
26: e8 00 00 00 00 callq 0 <__Z4testv+0x2B>
2b: c7 05 1c 00 00 00 00 00 00 00 movl $0, 28(%rip)
35: 48 8d 05 39 00 00 00 leaq 57(%rip), %rax
3c: 48 89 05 10 00 00 00 movq %rax, 16(%rip)
43: c7 05 14 00 00 00 23 00 00 00 movl $35, 20(%rip)
4d: c7 05 1c 00 00 00 01 00 00 00 movl $1, 28(%rip)
57: e8 00 00 00 00 callq 0 <__Z4testv+0x5C>
5c: c7 05 1c 00 00 00 00 00 00 00 movl $0, 28(%rip)
66: 5d popq %rbp
67: c3 retq
具體而言,在2b
的movl $0, 28(%rip)
指令仍然存在。
巧合的是,這個輸出正是我需要在我的案件。但我不知道我是否可以依靠它,因爲根據我的理解,這不能保證我選擇的記憶順序。
所以我的主要問題是:是否獲取釋放內存爲了給我,我不知道其他(幸運的)保證?或者是編譯器只是做我偶然需要的東西/因爲它不會優化這個特定的情況以及它可以?
全部下面的代碼:
// clang++ -c main.cpp -std=c++11 -O3 && objdump -d main.o
#include <atomic>
#include <cstdint>
struct StackFrame
{
const char* functionName;
uint32_t lineNumber;
};
struct Stack
{
Stack()
: stackFrames{ StackFrame{ nullptr, 0 }, StackFrame{ nullptr, 0 } }
, stackTop{0}
{
}
StackFrame stackFrames[2];
std::atomic<uint32_t> stackTop;
};
Stack theStack;
void someFunction();
void someOtherFunction();
void test() {
theStack.stackFrames[1] = StackFrame{ "someFunction", 30 };
theStack.stackTop.store(1, std::memory_order_release);
someFunction();
theStack.stackTop.store(0, std::memory_order_release);
theStack.stackFrames[1] = StackFrame{ "someOtherFunction", 35 };
theStack.stackTop.store(1, std::memory_order_release);
someOtherFunction();
theStack.stackTop.store(0, std::memory_order_release);
}
/**
* // Sampler thread:
*
* #include <chrono>
* #include <iostream>
* #include <thread>
*
* void suspendTargetThread();
* void unsuspendTargetThread();
*
* void samplerThread() {
* for (;;) {
* // Suspend the target thread. This uses a platform-specific
* // mechanism:
* // - SuspendThread on Windows
* // - thread_suspend on macOS
* // - send a signal + grab a lock in the signal handler on Linux
* suspendTargetThread();
*
* // Now that the thread is paused, read the leaf stack frame.
* uint32_t stackTop =
* theStack.stackTop.load(std::memory_order_acquire);
* StackFrame& f = theStack.stackFrames[stackTop];
* std::cout << f.functionName << " at line "
* << f.lineNumber << std::endl;
*
* unsuspendTargetThread();
*
* std::this_thread::sleep_for(std::chrono::milliseconds(1));
* }
* }
*/
而且,爲了滿足好奇心,這是組裝如果我使用順序一致店:
$ clang++ -c main.cpp -std=c++11 -O3 && objdump -d main.o
main.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
__Z4testv:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 41 56 pushq %r14
6: 53 pushq %rbx
7: 48 8d 05 60 00 00 00 leaq 96(%rip), %rax
e: 48 89 05 10 00 00 00 movq %rax, 16(%rip)
15: c7 05 14 00 00 00 1e 00 00 00 movl $30, 20(%rip)
1f: 41 be 01 00 00 00 movl $1, %r14d
25: b8 01 00 00 00 movl $1, %eax
2a: 87 05 20 00 00 00 xchgl %eax, 32(%rip)
30: e8 00 00 00 00 callq 0 <__Z4testv+0x35>
35: 31 db xorl %ebx, %ebx
37: 31 c0 xorl %eax, %eax
39: 87 05 20 00 00 00 xchgl %eax, 32(%rip)
3f: 48 8d 05 35 00 00 00 leaq 53(%rip), %rax
46: 48 89 05 10 00 00 00 movq %rax, 16(%rip)
4d: c7 05 14 00 00 00 23 00 00 00 movl $35, 20(%rip)
57: 44 87 35 20 00 00 00 xchgl %r14d, 32(%rip)
5e: e8 00 00 00 00 callq 0 <__Z4testv+0x63>
63: 87 1d 20 00 00 00 xchgl %ebx, 32(%rip)
69: 5b popq %rbx
6a: 41 5e popq %r14
6c: 5d popq %rbp
6d: c3 retq
儀器確定的xchgl
說明作爲最昂貴的部分。
我打算通過使'functionName'和'獲得第二個保障lineNumber'原子,以及使用它們的發佈商店。這似乎沒有影響生成的代碼,所以我認爲它會沒事的。我只是想知道這是否真的有必要。 –
在討論二叉樹時,通常使用術語*葉*嗎?它在這裏意味着什麼? – VTT
我會將它重命名爲「stackTop」以使其更清晰。 –