概况
对带宽,CPU使用率和GPU使用率极为密集的应用程序需要从一个GPU到另一个GPU每秒传输大约10-15GB.它使用DX11 API访问GPU,因此只有使用需要映射的缓冲区才能上传到GPU.上传一次发生在25MB的块中,并且16个线程同时将缓冲区写入映射的缓冲区.关于这一点,没有什么可以做的.如果不是针对以下错误,则写入的实际并发级别应该更低.
它是一个功能强大的工作站,配有3个Pascal GPU,一个高端Haswell处理器和四通道RAM.在硬件上没有太多改进.它正在运行Windows 10的桌面版.
实际问题
一旦我通过~50%的CPU负载,MmPageFault()(在Windows内核中,在访问已映射到您的地址空间但尚未由操作系统提交的内存时调用)中的某些内容突然破坏,剩下的50%CPU负载是被浪费在内部的旋转锁上MmPageFault().CPU变得100%利用,应用程序性能完全降低.
我必须假设这是由于每秒需要分配给进程的大量内存,并且每次取消映射DX11缓冲区时也完全从进程中取消映射.相应地,它实际上是MmPageFault()每秒数千次调用,顺序发生,顺序memcpy()写入缓冲区.对于遇到的每个单独的未提交页面.
一个CPU负载超过50%,保护页面管理的Windows内核中的乐观自旋锁完全降低了性能.
注意事项
缓冲区由DX11驱动程序分配.没有什么可以调整分配策略.不可能使用不同的内存API,尤其是重用.
调用DX11 API(映射/取消映射缓冲区)都是从单个线程发生的.与系统中的虚拟处理器相比,实际的复制操作可能跨越更多线程发生多线程.
无法降低内存带宽要求.这是一个实时应用程序.事实上,硬限制目前是主GPU的PCIe 3.0 16x带宽.如果可以的话,我已经需要进一步推进.
避免使用多线程副本是不可能的,因为有些独立的生产者 - 消费者队列无法简单地合并.
旋转锁定性能下降似乎非常罕见(因为用例推动它那么远)在Google上,你不会找到旋转锁定功能名称的单一结果.
升级到可以更好地控制映射的API(Vulkan)正在进行中,但它不适合作为短期修复.出于同样的原因,切换到更好的OS内核目前不是一种选择.
减少CPU负载也不起作用; 除了(通常是微不足道的和便宜的)缓冲区副本之外,还有太多的工作需要完成.
问题
可以做些什么?
我需要显着减少单个页面故障的数量.我知道已映射到我的进程的缓冲区的地址和大小,我也知道内存尚未提交.
如何确保以尽可能少的事务提交内存?
DX11的异常标志可以防止在取消映射后取消分配缓冲区,Windows API强制在单个事务中提交,几乎任何东西都是受欢迎的.
目前的状态
// In the processing threads
{
DX11DeferredContext->Map(..., &buffer)
std::memcpy(buffer, source, size);
DX11DeferredContext->Unmap(...);
}
Run Code Online (Sandbox Code Playgroud) 让我们从一个简单的例子开始:
std::vector<int> foo;
... // fill it
auto begin = foo.begin();
auto end = foo.end();
auto middle = begin + std::distance(begin, end) / 2;
Run Code Online (Sandbox Code Playgroud)
如果迭代器具有LegacyRandomAccessIterator特征,迭代器范围的理想双分区是非常容易的,因此任何形式的分而治之类型的算法也是如此。这种模式在 STL 实现中也很常见。
如果他们只提供LegacyForwardIteratortrait,那么我们在恒定时间内唯一能做的就是:
std::set<int> foo;
... // fill it
auto begin = foo.begin();
auto end = foo.end();
auto middle = (begin != end) ? ++(begin) : begin;
Run Code Online (Sandbox Code Playgroud)
如果没有线性扫描,就没有机会以远程平衡的方式处理迭代器范围。
很容易理解,对于大多数不提供 的容器LegacyRandomAccessIterator,理想的分区方案不可能是免费的。但是,大多数容器仍然能够以1:n-1恒定成本提供比平均更好的分区方案。
任何基于自平衡树的容器都能够根据内在的实现细节保证最坏情况的比率。任何哈希表——无论是基于桶还是基于多轮——仍然有很高的机会提供一个好的分区,只需将哈希表本身切成两半。
只有不平衡的树或普通列表在设计上是不合适的。
这种不足也显示在 STL 内部算法中,这取决于某种平衡的划分,例如std::for_each(std::execution::par_unseq, ...). 尽管几乎所有实现中的大多数 STL 容器都有实现细节,允许将恒定时间划分为远程平衡块,但我还没有看到任何 STL 实现处理任何东西,除了LegacyRandomAccessIterator比纯单元素循环更好。通常会导致缓存行的错误共享,以及远远超出任何实际价值的同步开销。
这就引出了一个问题,为什么 C++ …
以下代码成功编译了大多数现代C++ 11兼容编译器(GCC> = 5.x,Clang,ICC,MSVC).
#include <string>
struct A
{
explicit A(const char *) {}
A(std::string) {}
};
struct B
{
B(A) {}
B(B &) = delete;
};
int main( void )
{
B b1({{{"test"}}});
}
Run Code Online (Sandbox Code Playgroud)
但是为什么它首先编译,以及列出的编译器如何解释该代码?
为什么MSVC能够在没有的情况下编译它B(B &) = delete;,但其他3个编译器都需要它?
为什么在删除复制构造函数的不同签名时,除了MSVC之外的所有编译器都会失败,例如B(const B &) = delete;?
编译器甚至都选择相同的构造函数吗?
为什么Clang会发出以下警告?
17 : <source>:17:16: warning: braces around scalar initializer [-Wbraced-scalar-init]
B b1({{{"test"}}});
Run Code Online (Sandbox Code Playgroud) 我的初始数据集是:
{'ID': [Row(userid=17562323, gross_merchandise_value=6072210944, country=u'ID'), Row(userid=29989283, gross_merchandise_value=4931252224, country=u'ID')]
Run Code Online (Sandbox Code Playgroud)
dict 值的类型是 pyspark.sql.types.Row
如何将dict转换为userid列表?像下面这样:
[17562323, 29989283],
Run Code Online (Sandbox Code Playgroud)
只需获取用户 ID 列表。
以下问题的用例是部署的宏包,因此输入完全是用户定义的。
考虑以下最小示例:
function(foo)
cmake_parse_arguments(FOO "" "POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH" "" ${ARGN})
# Chosen as an example for a necessary step which doesn't have a generator expression equivalent
get_filename_component(
ABSOLUTE_PATH
"${FOO_POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH}"
REALPATH
BASE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}"
)
# Compatible with generator expressions by itself
add_custom_command(
OUTPUT
${MY_OUTPUT}
COMMAND
${MY_TOOL}
${ABSOLUTE_PATH}
)
endfunction()
Run Code Online (Sandbox Code Playgroud)
add_custom_command itself supports generator expressions, but the need to go via get_filename_component to handle one class of input (relative paths, for brevity in calling code), and the use of that intermediate step being impossible …