我需要一个upper_bound
的实现,如STL中针对我的金属计算内核所描述的那样。在金属标准库中没有任何内容,我基本上将它从<algorithm>
复制到我的着色器文件中,如下所示:
static device float* upper_bound( device float* first, device float* last, float val)
{
ptrdiff_t count = last - first;
while( count > 0){
device float* it = first;
ptrdiff_t step = count/2;
it += step;
if( !(val < *it)){
first = ++it;
count -= step + 1;
}else count = step;
}
return first;
}
我创建了一个简单的内核来测试它:
kernel void upper_bound_test(
device float* input [[buffer(0)]],
device uint* output [[buffer(1)]]
)
{
device float* where = upper_bound( input, input + 5, 3.1);
output[0] = where - input;
}
此测试具有硬编码输入大小和搜索值。我还在框架端硬编码了一个5元素输入缓冲区,如下所示。我期望这个内核返回第一个输入的索引大于3.1
它不起作用。事实上,output[0]
永远不会被写入 - 因为我使用幻数预加载缓冲区以查看它是否被覆盖。它没有。实际上在waitUntilCompleted
之后,commandBuffer.error
看起来像这样:
Error Domain = MTLCommandBufferErrorDomain
Code = 1
NSLocalizedDescription = "IOAcceleratorFamily returned error code 3"
错误代码3是什么意思?我的内核在它有机会完成之前被杀了吗?
此外,我尝试了upper_bound
的线性搜索版本,如下所示:
static device float* upper_bound2( device float* first, device float* last, float val)
{
while( first < last && *first <= val)
++first;
return first;
}
这个有效(排序)。我对来自<algorithm>
的二分搜索lower_bound有同样的问题 - 但是一个天真的线性版本可以工作(排序)。顺便说一下,我用直接的C代码测试了我的STL复制版本(显然删除了device
)并且它们在着色器之外工作正常。请告诉我,我做错了,这不是金属编译器的错误。
现在关于上面的“排序”:线性搜索版本适用于5s和mini-2(A7s)(在上面的示例中返回索引3),但是在6+(A8)上它给出正确的回答+ 2 ^ 31。有没有搞错!相同的代码。关于框架方面的注意事项我使用uint32_t
,而在着色器方面我使用uint
- 这是相同的事情。另请注意,每个指针减法(ptrdiff_t
都是带符号的8字节事物)都是小的非负值。为什么6+设置高位有效?当然,为什么我的真正的二进制搜索版本不起作用?
以下是框架方面的内容:
id<MTLFunction> upperBoundTestKernel = [_library newFunctionWithName: @"upper_bound_test"];
id <MTLComputePipelineState> upperBoundTestPipelineState = [_device
newComputePipelineStateWithFunction: upperBoundTestKernel
error: &err];
float sortedNumbers[] = {1., 2., 3., 4., 5.};
id<MTLBuffer> testInputBuffer = [_device
newBufferWithBytes:(const void *)sortedNumbers
length: sizeof(sortedNumbers)
options: MTLResourceCPUCacheModeDefaultCache];
id<MTLBuffer> testOutputBuffer = [_device
newBufferWithLength: sizeof(uint32_t)
options: MTLResourceCPUCacheModeDefaultCache];
*(uint32_t*)testOutputBuffer.contents = 42;//magic number better get clobbered
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
id<MTLComputeCommandEncoder> commandEncoder = [commandBuffer computeCommandEncoder];
[commandEncoder setComputePipelineState: upperBoundTestPipelineState];
[commandEncoder setBuffer: testInputBuffer offset: 0 atIndex: 0];
[commandEncoder setBuffer: testOutputBuffer offset: 0 atIndex: 1];
[commandEncoder
dispatchThreadgroups: MTLSizeMake( 1, 1, 1)
threadsPerThreadgroup: MTLSizeMake( 1, 1, 1)];
[commandEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
uint32_t answer = *(uint32_t*)testOutputBuffer.contents;
答案 0 :(得分:0)
好吧,我找到了解决方案/解决方案。我猜这是一个指针别名问题,因为first
和last
指向同一个缓冲区。所以我将它们从单个指针变量更改为偏移量。这是一个重写的upper_bound2:
static uint upper_bound2( device float* input, uint first, uint last, float val)
{
while( first < last && input[first] <= val)
++first;
return first;
}
重新编写测试内核:
kernel void upper_bound_test(
device float* input [[buffer(0)]],
device uint* output [[buffer(1)]]
)
{
output[0] = upper_bound2( input, 0, 5, 3.1);
}
这完全奏效了。也就是说,它不仅修复了&#34;排序&#34;线性搜索的问题,但类似的重写二进制搜索也有效。我不想相信这一点。金属着色器语言应该是C ++的一个子集,但标准指针语义不起作用?我真的不能比较或减少指针吗?
无论如何,我不记得看到任何文档说没有指针别名或什么声明咒语会帮助我。还有什么帮助?
[UPDATE]
正如记录所述,&#34;史莱姆&#34;在Apple的开发论坛上: https://developer.apple.com/library/ios/documentation/Metal/Reference/MetalShadingLanguageGuide/func-var-qual/func-var-qual.html#//apple_ref/doc/uid/TP40014364-CH4-SW3
&#34;指定为图形或内核函数的参数值的缓冲区(设备和常量)不能别名 - 也就是说,作为参数值传递的缓冲区不能与传递给相同图形的单独参数的另一个缓冲区重叠或核心功能。&#34;
但同样值得注意的是,upper_bound()不是内核函数,而upper_bound_test()不会传递别名参数。 upper_bound_test()所做的是创建一个本地临时文件,该临时文件指向与其参数之一相同的缓冲区。也许文档应该说出它意味着什么,例如:&#34;没有设备的指针别名和任何函数中的常量内存,包括rvalues。&#34;我实际上并不知道这是否太强了。