如何优化从半精度float16到单精度float32的转换?
我正在尝试提高我的功能性能。 Profiler指向内循环的代码。我可以使用SSE内在函数来改善该代码的性能吗?
void ConvertImageFrom_R16_FLOAT_To_R32_FLOAT(char* buffer, void* convertedData, DWORD width, DWORD height, UINT rowPitch)
{
struct SINGLE_FLOAT
{
union {
struct {
unsigned __int32 R_m : 23;
unsigned __int32 R_e : 8;
unsigned __int32 R_s : 1;
};
struct {
float r;
};
};
};
C_ASSERT(sizeof(SINGLE_FLOAT) == 4); // 4 bytes
struct HALF_FLOAT
{
unsigned __int16 R_m : 10;
unsigned __int16 R_e : 5;
unsigned __int16 R_s : 1;
};
C_ASSERT(sizeof(HALF_FLOAT) == 2);
SINGLE_FLOAT* d = (SINGLE_FLOAT*)convertedData;
for(DWORD j = 0; j< height; j++)
{
HALF_FLOAT* s = (HALF_FLOAT*)((char*)buffer + rowPitch * j);
for(DWORD i = 0; i< width; i++)
{
d->R_s = s->R_s;
d->R_e = s->R_e - 15 + 127;
d->R_m = s->R_m << (23-10);
d++;
s++;
}
}
}
更新:
拆卸
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01
TITLE Utils.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC ?ConvertImageFrom_R16_FLOAT_To_R32_FLOAT@@YAXPADPAXKKI@Z ; ConvertImageFrom_R16_FLOAT_To_R32_FLOAT
; Function compile flags: /Ogtp
; COMDAT ?ConvertImageFrom_R16_FLOAT_To_R32_FLOAT@@YAXPADPAXKKI@Z
_TEXT SEGMENT
_buffer$ = 8 ; size = 4
tv83 = 12 ; size = 4
_convertedData$ = 12 ; size = 4
_width$ = 16 ; size = 4
_height$ = 20 ; size = 4
_rowPitch$ = 24 ; size = 4
?ConvertImageFrom_R16_FLOAT_To_R32_FLOAT@@YAXPADPAXKKI@Z PROC ; ConvertImageFrom_R16_FLOAT_To_R32_FLOAT, COMDAT
; 323 : {
push ebp
mov ebp, esp
; 343 : for(DWORD j = 0; j< height; j++)
mov eax, DWORD PTR _height$[ebp]
push esi
mov esi, DWORD PTR _convertedData$[ebp]
test eax, eax
je SHORT $LN4@ConvertIma
; 324 : union SINGLE_FLOAT {
; 325 : struct {
; 326 : unsigned __int32 R_m : 23;
; 327 : unsigned __int32 R_e : 8;
; 328 : unsigned __int32 R_s : 1;
; 329 : };
; 330 : struct {
; 331 : float r;
; 332 : };
; 333 : };
; 334 : C_ASSERT(sizeof(SINGLE_FLOAT) == 4);
; 335 : struct HALF_FLOAT
; 336 : {
; 337 : unsigned __int16 R_m : 10;
; 338 : unsigned __int16 R_e : 5;
; 339 : unsigned __int16 R_s : 1;
; 340 : };
; 341 : C_ASSERT(sizeof(HALF_FLOAT) == 2);
; 342 : SINGLE_FLOAT* d = (SINGLE_FLOAT*)convertedData;
push ebx
mov ebx, DWORD PTR _buffer$[ebp]
push edi
mov DWORD PTR tv83[ebp], eax
$LL13@ConvertIma:
; 344 : {
; 345 : HALF_FLOAT* s = (HALF_FLOAT*)((char*)buffer + rowPitch * j);
; 346 : for(DWORD i = 0; i< width; i++)
mov edi, DWORD PTR _width$[ebp]
mov edx, ebx
test edi, edi
je SHORT $LN5@ConvertIma
npad 1
$LL3@ConvertIma:
; 347 : {
; 348 : d->R_s = s->R_s;
movzx ecx, WORD PTR [edx]
movzx eax, WORD PTR [edx]
shl ecx, 16 ; 00000010H
xor ecx, DWORD PTR [esi]
shl eax, 16 ; 00000010H
and ecx, 2147483647 ; 7fffffffH
xor ecx, eax
mov DWORD PTR [esi], ecx
; 349 : d->R_e = s->R_e - 15 + 127;
movzx eax, WORD PTR [edx]
shr eax, 10 ; 0000000aH
and eax, 31 ; 0000001fH
add eax, 112 ; 00000070H
shl eax, 23 ; 00000017H
xor eax, ecx
and eax, 2139095040 ; 7f800000H
xor eax, ecx
mov DWORD PTR [esi], eax
; 350 : d->R_m = s->R_m << (23-10);
movzx ecx, WORD PTR [edx]
and ecx, 1023 ; 000003ffH
shl ecx, 13 ; 0000000dH
and eax, -8388608 ; ff800000H
or ecx, eax
mov DWORD PTR [esi], ecx
; 351 : d++;
add esi, 4
; 352 : s++;
add edx, 2
dec edi
jne SHORT $LL3@ConvertIma
$LN5@ConvertIma:
; 343 : for(DWORD j = 0; j< height; j++)
add ebx, DWORD PTR _rowPitch$[ebp]
dec DWORD PTR tv83[ebp]
jne SHORT $LL13@ConvertIma
pop edi
pop ebx
$LN4@ConvertIma:
pop esi
; 353 : }
; 354 : }
; 355 : }
pop ebp
ret 0
?ConvertImageFrom_R16_FLOAT_To_R32_FLOAT@@YAXPADPAXKKI@Z ENDP ; ConvertImageFrom_R16_FLOAT_To_R32_FLOAT
_TEXT ENDS
没有找到相关结果
已邀请:
10 个回复
先对冈蒲
靛新比比催
)。 从Intel IvyBridge和AMD Piledriver开始支持F16C。 (并且有自己的CPUID功能位,您的代码应该检查它,否则回退到SIMD整数移位和混洗)。 VCVTPS2PH的内在函数是:
直接字节是舍入控制。编译器可以将它直接用作转换存储器(与大多数可以选择使用内存操作数的指令不同,它是源操作数,可以是内存而不是寄存器。) VCVTPH2PS采用另一种方式,就像大多数其他SSE指令一样(可以在寄存器之间使用或作为负载使用)。
F16C非常高效,您可能需要考虑将图像保留为半精度格式,并在每次需要来自它的数据向量时即时转换。这非常适合您的缓存占用空间。
课刊灭似
变量。 有些处理器不喜欢从内存中获取常量;它很尴尬,可能需要很多指令周期。 循环展开 重复循环中的语句,并增加增量。 处理器更喜欢连续的指令;跳跃和分支愤怒他们。 数据预取(或加载缓存) 在循环中使用更多变量,并将它们声明为
,因此编译器不会优化它们:
习让休堂溯
贸会
体悉
掏得透垦滩
递劝臼类洪
窝头菊
皇小福另届
和
,但每次迭代使用
和
。 (然后当然在每条扫描线之后碰撞
。)由于
的元素是4字节而
2的元素,因此该操作可以折叠到地址计算中。 (不幸的是,我不能保证这会使执行更有效率。) 删除位域操作并手动执行操作。 (当提取时,先移位并屏蔽第二个,以最大化掩模可以适应小的立即值的可能性。) 展开循环,虽然循环很容易预测,但这可能没什么区别。 沿着从
到0的每条线计数。这会阻止编译器每次都要获取
。对于x86来说可能更重要,因为它的寄存器很少。 (如果CPU喜欢我的“
和
”建议,你可以将宽度签名,从
开始计数,然后向后走。) 这些都比尝试转换为SSE更快,并且希望能使它受内存限制,如果还没有,那么你可以放弃。 最后,如果输出是写入组合存储器(例如,它是纹理或顶点缓冲区或通过AGP或PCI Express访问的东西,或者PC现在拥有的任何东西)那么这很可能导致性能不佳,具体取决于什么编译器为内部循环生成的代码。因此,如果是这种情况,您可以获得更好的结果,将每个扫描线转换为本地缓冲区,然后使用
将其复制到其最终目的地。