我们为编写的多媒体匹配项目编写了一个自定义索引引擎C#.
索引引擎是以非托管方式编写的C++,可以以std::集合和容器的形式保存大量非托管内存.
每个非托管索引实例都由托管对象包装; unamanaged索引的生存期由托管包装器的生命周期控制.
我们已经确保(通过自定义,跟踪C++分配器)正在考虑索引内部消耗的每个字节,并且我们使用此值的增量更新(每秒10次)托管垃圾收集器的内存压力值(积极的三角洲呼叫GC.AddMemoryPressure(),负的三角洲呼叫GC.RemoveMemoryPressure()).
这些索引是线程安全的,并且可以由许多C#worker共享,因此可能有多个引用用于同一索引.出于这个原因,我们无法Dispose()自由调用,而是依赖垃圾收集器来跟踪引用共享,并最终在工作进程未使用它们时触发索引的最终确定.
现在,问题是我们的内存不足.事实上,完整集合通常是相对频繁地执行的,但是,在内存分析器的帮助下,我们可以发现在完成后队列中,在进程耗尽后,内存中存在大量"死"索引实例.分页文件.
如果我们添加一个看门狗线程,GC::WaitForPendingFinalizers()然后调用GC::Collect()低内存条件,我们实际上可以避免这个问题 ,但是,从我们读过的内容,GC::Collect()手动调用会严重破坏垃圾收集效率,我们不希望如此.
我们甚至添加了一个悲观的压力因素(尝试高达4倍)来夸大报告给.net端的非托管内存量,看看我们是否可以哄骗垃圾收集器来更快地清空队列.似乎处理队列的线程完全没有意识到内存压力.
在这一点上,我们觉得我们需要Dispose()在计数达到零时实现手动引用计数,但这似乎是一种矫枉过正,特别是因为内存压力API的整个目的正是为了解决像我们这样的情况.
一些事实:
欢迎任何想法或建议
我正在尝试编写非常有效的汉明距离代码.受WojciechMuła非常聪明的SSE3 popcount 实现的启发,我编写了一个AVX2等效解决方案,这次使用256位寄存器. 基于所涉及的操作的双倍并行性,我预计至少会有30%-40%的改进,但令我惊讶的是,AVX2代码稍慢(约2%)!
有人可以告诉我可能的原因,为什么我没有获得预期的性能提升?
展开,两个64字节块的SSE3汉明距离:
INT32 SSE_PopCount(const UINT32* __restrict pA, const UINT32* __restrict pB) {
__m128i paccum = _mm_setzero_si128();
__m128i a = _mm_loadu_si128 (reinterpret_cast<const __m128i*>(pA));
__m128i b = _mm_loadu_si128 (reinterpret_cast<const __m128i*>(pB));
__m128i err = _mm_xor_si128 (a, b);
__m128i lo = _mm_and_si128 (err, low_mask);
__m128i hi = _mm_srli_epi16 (err, 4);
hi = _mm_and_si128 (hi, low_mask);
__m128i popcnt1 = _mm_shuffle_epi8(lookup, lo);
__m128i popcnt2 = _mm_shuffle_epi8(lookup, hi);
paccum = _mm_add_epi8(paccum, popcnt1);
paccum = _mm_add_epi8(paccum, popcnt2);
a = _mm_loadu_si128 (reinterpret_cast<const …Run Code Online (Sandbox Code Playgroud) 我正在尝试优化一种算法,该算法将处理可能受益于AVX SIMD指令的大量数据集.不幸的是,输入存储器布局对于所需的计算并不是最佳的.必须通过组合__m256i恰好相隔4个字节的单个字节的值来重新排序信息:
开始编辑
我的目标CPUS不支持AVX2指令,所以像@Elalfer和@PeterCordes指出的那样,我不能使用__m256i值,代码必须转换为使用__m128i值而不是)
结束编辑
内存中的DataSet布局
Byte 0 | Byte 1 | Byte 2 | Byte 3
Byte 4 | Byte 5 | Byte 6 | Byte 7
...
Byte 120 | Byte 121 | Byte 122 | Byte 123
Byte 124 | Byte 125 | Byte 126 | Byte 127
Run Code Online (Sandbox Code Playgroud)
__m256i变量中的期望值:
| Byte 0 | Byte 4 | Byte 8 | ... | Byte 120 | Byte 124 |
Run Code Online (Sandbox Code Playgroud)
除了这个简单的代码之外,是否有更有效的方法来收集和重新排列跨步数据?
union { __m256i reg; uint8_t bytes[32]; …Run Code Online (Sandbox Code Playgroud) 当从连续的内存位置执行一系列_mm_stream_load_si128()调用(MOVNTDQA)时,硬件预取器是否仍会启动,或者我应该使用显式软件预取(使用NTA提示)以获得预取的好处,同时仍然避免缓存污染?
我问这个的原因是因为他们的目标似乎与我相矛盾.流加载将获取绕过缓存的数据,而预取器尝试主动将数据提取到缓存中.
当顺序迭代一个大型数据结构(处理过的数据不会在很长一段时间内被修饰)时,我有必要避免污染chache层次结构,但我不想因频繁出现频繁的~100次循环处罚-fetcher闲置.
目标架构是Intel SandyBridge
如果我有一个 .NET Standard 2.0 库项目正在被 .NET 6.0 控制台项目使用,那么如果我还指示编译器生成该库的 .NET 6.0 版本,是否会有任何性能优势?
我不打算使用 .NET 6.0 上提供的任何功能,我只是想知道 .NET 6.0 版本是否受到编译器的额外喜爱。
我刚刚测试了一些我确信会失败的东西,但令我惊讶的是,它完美无瑕地工作,并向我自己证明我仍然对async-await工作的方式感到很困惑.
我创建了一个线程,将async void委托作为线程的主体传递.这是我的代码过度简化:
var thread = new Thread( async () => {
while( true ) {
await SomeLengthyTask();
...
}
});
thread.Start();
thread.Join();
Run Code Online (Sandbox Code Playgroud)
问题是,据我所知,当执行命中await关键字时,从方法中隐式返回,在这种情况下是循环线程的主体,而其余代码包含在回调延续中.
由于这个事实,我很确定线程会在await产生执行后立即终止,但事实并非如此!
有谁知道这个魔法是如何实际实现的?在async功能精简和async同步等待或者是有一些黑魔法将使它能够恢复已因为一次产生一个线程CLR正在做await?
查看该Expression.Call(),方法可用的文档重载,我可以找到以下重载来获取一个表达式节点,该节点将执行对期望的实例方法的调用:
Expression数组的变量参数IEnumerable<Expression>没有超载期望单个参数的理由是什么?
在我看来,单个参数案例的方法签名是:
public static MethodCallExpression Call(
Expression instance,
MethodInfo method,
Expression arg0);
Run Code Online (Sandbox Code Playgroud)
我没有看到任何其他重载会与此方法签名冲突,所以我真的不明白为什么该方法丢失.我明白,期望一个数组或一个数组的重载IEnumerable将允许我创建一个Expression单参数的情况,但这也适用于其他可用的重载,所以我很好奇,如果有什么我看不到会解释为什么这种重载缺失了.
c# ×3
.net ×2
c++ ×2
performance ×2
sse ×2
.net-6.0 ×1
async-await ×1
asynchronous ×1
avx ×1
avx2 ×1
c ×1
c#-5.0 ×1
cpu-cache ×1
intrinsics ×1
prefetch ×1
x86 ×1