在c ++中将4个字节转换为float的最快方法

JLe*_*Lev 1 c++ arrays floating-point optimization casting

我需要将一个字节数组转换为浮点数组.我通过网络连接获取字节,然后需要将它们解析为浮点数.阵列的大小没有预定义.这是我到目前为止使用工会的代码.您对如何让它运行得更快有什么建议吗?

int offset = DATA_OFFSET - 1;
UStuff bb;
//Convert every 4 bytes to float using a union
for (int i = 0; i < NUM_OF_POINTS;i++){
    //Going backwards - due to endianness
    for (int j = offset + BYTE_FLOAT*i + BYTE_FLOAT ; j > offset + BYTE_FLOAT*i; --j)
    {
        bb.c[(offset + BYTE_FLOAT*i + BYTE_FLOAT)- j] = sample[j];
    }
    res.append(bb.f);
}
return res;
Run Code Online (Sandbox Code Playgroud)

这是我使用的联盟

union UStuff
{
        float   f;
        unsigned char   c[4];
};
Run Code Online (Sandbox Code Playgroud)

Bat*_*eba 6

从技术上讲,你不允许union在C++中输入-pin ,尽管你可以在C中进行操作.你的代码的行为是未定义的.

抛开这个重要的行为点:即便如此,你假设a float在所有机器上以相同的方式表示,而不是.你可能会觉得一个float是数据的32位IEEE754小端块,但它不具备对是.

可悲的是,最好的解决方案最终会变慢.浮点数据的大多数序列化是通过进入和退出字符串来执行的,在您的情况下几乎可以解决问题,因为您可以将它们表示为unsigned char数据数组.所以你唯一的争论就是找出数据的编码.任务完成!


小智 6

简答

#include <cstdint>
#define NOT_STUPID 1
#define ENDIAN NOT_STUPID

namespace _ {
inline uint32_t UI4Set(char byte_0, char byte_1, char byte_2, char byte_3) {
#if ENDIAN == NOT_STUPID
  return byte_0 | ((uint32_t)byte_1) << 8 | ((uint32_t)byte_2) << 16 |
         ((uint32_t)byte_3) << 24;
#else
  return byte_3 | ((uint32_t)byte_2) << 8 | ((uint32_t)byte_1) << 16 |
         ((uint32_t)byte_0) << 24;
#endif
}

inline float FLTSet(char byte_0, char byte_1, char byte_2, char byte_3) {
  uint32_t flt = UI4Set(byte_0, byte_1, byte_2, byte_3);
  return *reinterpret_cast<float*>(&flt);
}

/* Use this function to write directly to RAM and avoid the xmm
   registers. */
inline uint32_t FLTSet(char byte_0, char byte_1, char byte_2, char byte_3,
                   float* destination) {
  uint32_t value = UI4Set (byte_0, byte_1, byte_2, byte_3);
  *reinterpret_cast<uint32_t*>(destination) = value;
  return value;
}

} //< namespace _
using namespace _; //< @see Kabuki Toolkit

static flt = FLTSet (0, 1, 2, 3);

int main () {
  uint32_t flt_init = FLTSet (4, 5, 6, 7, &flt);
  return 0;
}
//< This uses 4 extra bytes doesn't use the xmm register
Run Code Online (Sandbox Code Playgroud)

长答案

一般建议使用 Union 来进行浮点和整数之间的转换,因为时至今日,Union 并不总是能生成最佳的汇编代码,而且其他技术更加明确,并且可能会使用更少的类型;我们将证明它可以用现代编译器进行反汇编:Visual-C++ 2018,而不是我从其他关于 Unions 的 StackOverflow 帖子中得到的观点。

关于如何优化浮点算法,我们首先必须了解寄存器的工作原理。CPU 的核心完全是一个带有协处理器(即扩展)的整数处理单元,用于处理浮点数。这些加载存储机 (LSM) 只能处理整数,并且它们必须使用一组单独的寄存器来与浮点协处理器交互。在 x86_64 上,这些是 xmm 寄存器,它们是 128 位宽,可以处理单指令多数据 (SIMD)。在 C++ 中,加载和存储浮点寄存器的方法是:

int Foo(double foo) { return foo + *reinterpret_cast<double*>(&foo); }

int main() {
  double foo = 1.0;
  uint64_t bar = *reinterpret_cast<uint64_t*>(&foo);
  return Foo(bar);
}
Run Code Online (Sandbox Code Playgroud)

现在让我们使用 Visual-C++ O2 优化来检查反汇编,因为如果没有它们,您将得到一堆调试堆栈帧变量。我必须将函数 Foo 添加到示例中,以避免代码被优化掉。

  double foo = 1.0;
  uint64_t bar = *reinterpret_cast<uint64_t*>(&foo);
00007FF7482E16A0  mov         rax,3FF0000000000000h  
00007FF7482E16AA  xorps       xmm0,xmm0  
00007FF7482E16AD  cvtsi2sd    xmm0,rax  
  return Foo(bar);
00007FF7482E16B2  addsd       xmm0,xmm0  
00007FF7482E16B6  cvttsd2si   eax,xmm0  
}
00007FF7482E16BA  ret  
Run Code Online (Sandbox Code Playgroud)

如上所述,我们可以看到 LSM 首先将 double 值移入整数寄存器,然后使用异或函数将 xmm0 寄存器清零,因为该寄存器是 128 位宽,而我们正在加载一个 64 位整数,然后使用该指令将整数寄存器的内容加载到浮点寄存器cvtsi2sd,最后跟随cvttsd2si指令将值从 xmm0 寄存器加载回返回寄存器,最后返回。

现在让我们解决使用此测试脚本和 Visual-C++ 2018 生成最佳汇编代码的问题:

#include <stdafx.h>
#include <cstdint>
#include <cstdio>

static float foo = 0.0f;

void SetFooUnion(char byte_0, char byte_1, char byte_2, char byte_3) {
  union {
    float flt;
    char bytes[4];

  } u = {foo};

  u.bytes[0] = byte_0;
  u.bytes[1] = byte_1;
  u.bytes[2] = byte_2;
  u.bytes[3] = byte_3;
  foo = u.flt;
}

void SetFooManually(char byte_0, char byte_1, char byte_2, char byte_3) {
  uint32_t faster_method = byte_0 | ((uint32_t)byte_1) << 8 |
                           ((uint32_t)byte_2) << 16 | ((uint32_t)byte_3) << 24;
  *reinterpret_cast<uint32_t*>(&foo) = faster_method;
}

namespace _ {
inline uint32_t UI4Set(char byte_0, char byte_1, char byte_2, char byte_3) {
  return byte_0 | ((uint32_t)byte_1) << 8 | ((uint32_t)byte_2) << 16 |
         ((uint32_t)byte_3) << 24;
}

inline float FLTSet(char byte_0, char byte_1, char byte_2, char byte_3) {
  uint32_t flt = UI4Set(byte_0, byte_1, byte_2, byte_3);
  return *reinterpret_cast<float*>(&flt);
}

inline void FLTSet(char byte_0, char byte_1, char byte_2, char byte_3,
                   float* destination) {
  uint32_t value = byte_0 | ((uint32_t)byte_1) << 8 | ((uint32_t)byte_2) << 16 |
                   ((uint32_t)byte_3) << 24;
  *reinterpret_cast<uint32_t*>(destination) = value;
}

}  // namespace _

int main() {
  SetFooUnion(0, 1, 2, 3);

  union {
    float flt;
    char bytes[4];

  } u = {foo};

  // Start union read tests

  putchar(u.bytes[0]);
  putchar(u.bytes[1]);
  putchar(u.bytes[2]);
  putchar(u.bytes[3]);

  // Start union write tests

  u.bytes[0] = 4;
  u.bytes[2] = 5;

  foo = u.flt;

  // Start hand-coded tests

  SetFooManually(6, 7, 8, 9);

  uint32_t bar = *reinterpret_cast<uint32_t*>(&foo);
  putchar((char)(bar));
  putchar((char)(bar >> 8));
  putchar((char)(bar >> 16));
  putchar((char)(bar >> 24));

  _::FLTSet (0, 1, 2, 3, &foo);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在,在检查 O2 优化的反汇编之后,我们已经证明编译器不会生成最佳代码:

int main() {
00007FF6DB4A1000  sub         rsp,28h  
  SetFooUnion(0, 1, 2, 3);
00007FF6DB4A1004  mov         dword ptr [rsp+30h],3020100h  
00007FF6DB4A100C  movss       xmm0,dword ptr [rsp+30h]  

  union {
    float flt;
    char bytes[4];

  } u = {foo};
00007FF6DB4A1012  movss       dword ptr [rsp+30h],xmm0  

  // Start union read tests

  putchar(u.bytes[0]);
00007FF6DB4A1018  movsx       ecx,byte ptr [u]  
  SetFooUnion(0, 1, 2, 3);
00007FF6DB4A101D  movss       dword ptr [foo (07FF6DB4A3628h)],xmm0  

  // Start union read tests

  putchar(u.bytes[0]);
00007FF6DB4A1025  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar(u.bytes[1]);
00007FF6DB4A102B  movsx       ecx,byte ptr [rsp+31h]  
00007FF6DB4A1030  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar(u.bytes[2]);
00007FF6DB4A1036  movsx       ecx,byte ptr [rsp+32h]  
00007FF6DB4A103B  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar(u.bytes[3]);
00007FF6DB4A1041  movsx       ecx,byte ptr [rsp+33h]  
00007FF6DB4A1046  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  

  uint32_t bar = *reinterpret_cast<uint32_t*>(&foo);
  putchar((char)(bar));
00007FF6DB4A104C  mov         ecx,6  

  // Start union write tests

  u.bytes[0] = 4;
  u.bytes[2] = 5;

  foo = u.flt;

  // Start hand-coded tests

  SetFooManually(6, 7, 8, 9);
00007FF6DB4A1051  mov         dword ptr [foo (07FF6DB4A3628h)],9080706h  

  uint32_t bar = *reinterpret_cast<uint32_t*>(&foo);
  putchar((char)(bar));
00007FF6DB4A105B  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar((char)(bar >> 8));
00007FF6DB4A1061  mov         ecx,7  
00007FF6DB4A1066  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar((char)(bar >> 16));
00007FF6DB4A106C  mov         ecx,8  
00007FF6DB4A1071  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  
  putchar((char)(bar >> 24));
00007FF6DB4A1077  mov         ecx,9  
00007FF6DB4A107C  call        qword ptr [__imp_putchar (07FF6DB4A2160h)]  

  return 0;
00007FF6DB4A1082  xor         eax,eax  

  _::FLTSet(0, 1, 2, 3, &foo);
00007FF6DB4A1084  mov         dword ptr [foo (07FF6DB4A3628h)],3020100h  
}
00007FF6DB4A108E  add         rsp,28h  
00007FF6DB4A1092  ret  
Run Code Online (Sandbox Code Playgroud)

这是原始的主要反汇编,因为缺少内联函数:

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.12.25831.0 

include listing.inc

INCLUDELIB OLDNAMES

EXTRN   __imp_putchar:PROC
EXTRN   __security_check_cookie:PROC
?foo@@3MA DD    01H DUP (?)             ; foo
_BSS    ENDS
PUBLIC  main
PUBLIC  ?SetFooManually@@YAXDDDD@Z          ; SetFooManually
PUBLIC  ?SetFooUnion@@YAXDDDD@Z             ; SetFooUnion
EXTRN   _fltused:DWORD
;   COMDAT pdata
pdata   SEGMENT
$pdata$main DD  imagerel $LN8
    DD  imagerel $LN8+137
    DD  imagerel $unwind$main
pdata   ENDS
;   COMDAT xdata
xdata   SEGMENT
$unwind$main DD 010401H
    DD  04204H
xdata   ENDS
; Function compile flags: /Ogtpy
; File c:\workspace\kabuki-toolkit\seams\0_0_experiments\main.cc
;   COMDAT ?SetFooManually@@YAXDDDD@Z
_TEXT   SEGMENT
byte_0$dead$ = 8
byte_1$dead$ = 16
byte_2$dead$ = 24
byte_3$dead$ = 32
?SetFooManually@@YAXDDDD@Z PROC             ; SetFooManually, COMDAT

  00000 c7 05 00 00 00
    00 06 07 08 09   mov     DWORD PTR ?foo@@3MA, 151521030 ; 09080706H

  0000a c3       ret     0
?SetFooManually@@YAXDDDD@Z ENDP             ; SetFooManually
_TEXT   ENDS
; Function compile flags: /Ogtpy
; File c:\workspace\kabuki-toolkit\seams\0_0_experiments\main.cc
;   COMDAT main
_TEXT   SEGMENT
u$1 = 48
u$ = 48
main    PROC                        ; COMDAT

$LN8:
  00000 48 83 ec 28  sub     rsp, 40            ; 00000028H

  00004 c7 44 24 30 00
    01 02 03     mov     DWORD PTR u$1[rsp], 50462976 ; 03020100H

  0000c f3 0f 10 44 24
    30       movss   xmm0, DWORD PTR u$1[rsp]

  00012 f3 0f 11 44 24
    30       movss   DWORD PTR u$[rsp], xmm0

  00018 0f be 4c 24 30   movsx   ecx, BYTE PTR u$[rsp]

  0001d f3 0f 11 05 00
    00 00 00     movss   DWORD PTR ?foo@@3MA, xmm0

  00025 ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  0002b 0f be 4c 24 31   movsx   ecx, BYTE PTR u$[rsp+1]
  00030 ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  00036 0f be 4c 24 32   movsx   ecx, BYTE PTR u$[rsp+2]
  0003b ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  00041 0f be 4c 24 33   movsx   ecx, BYTE PTR u$[rsp+3]
  00046 ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  0004c b9 06 00 00 00   mov     ecx, 6

  00051 c7 05 00 00 00
    00 06 07 08 09   mov     DWORD PTR ?foo@@3MA, 151521030 ; 09080706H

  0005b ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  00061 b9 07 00 00 00   mov     ecx, 7
  00066 ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  0006c b9 08 00 00 00   mov     ecx, 8
  00071 ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  00077 b9 09 00 00 00   mov     ecx, 9
  0007c ff 15 00 00 00
    00       call    QWORD PTR __imp_putchar

  00082 33 c0        xor     eax, eax

  00084 48 83 c4 28  add     rsp, 40            ; 00000028H
  00088 c3       ret     0
main    ENDP
_TEXT   ENDS
END
Run Code Online (Sandbox Code Playgroud)

那么区别是什么呢?

?SetFooUnion@@YAXDDDD@Z PROC                ; SetFooUnion, COMDAT
; File c:\workspace\kabuki-toolkit\seams\0_0_experiments\main.cc
; Line 7
    mov BYTE PTR [rsp+32], r9b
; Line 14
    mov DWORD PTR u$[rsp], 50462976     ; 03020100H
; Line 18
    movss   xmm0, DWORD PTR u$[rsp]
    movss   DWORD PTR ?foo@@3MA, xmm0
; Line 19
    ret 0
?SetFooUnion@@YAXDDDD@Z ENDP                ; SetFooUnion
Run Code Online (Sandbox Code Playgroud)

相对:

?SetFooManually@@YAXDDDD@Z PROC             ; SetFooManually, COMDAT
; File c:\workspace\kabuki-toolkit\seams\0_0_experiments\main.cc
; Line 34
    mov DWORD PTR ?foo@@3MA, 151521030      ; 09080706H
; Line 35
    ret 0
?SetFooManually@@YAXDDDD@Z ENDP             ; SetFooManually
Run Code Online (Sandbox Code Playgroud)

首先要注意的是 Union 对内联内存优化的影响。联合体专门设计用于在不同时间段为不同目的复用 RAM,以减少 RAM 使用量,因此这意味着内存必须在 RAM 中保持一致,从而减少内联性。Union 代码强制编译器将 Union 写入 RAM,而非 Union 方法只是抛出代码并用一条mov DWORD PTR ?foo@@3MA, 151521030指令替换,而不使用 xmm0 寄存器!O2 优化自动内联了 SetFooUnion 和 SetFooManually 函数,但非 Union 方法内联了更多代码,使用了更少的 RAM 读取,证据来自 Union 方法的代码行之间的差异:

movsx       ecx,byte ptr [rsp+31h]
Run Code Online (Sandbox Code Playgroud)

与非 Union 方法的版本相比:

mov         ecx,7  
Run Code Online (Sandbox Code Playgroud)

Union 正在从RAM 指针加载 ecx ,而另一个则使用更快的单周期 mov 指令。这是一个巨大的性能提升!然而,这实际上可能是使用实时系统和多线程应用程序时所需的行为,因为编译器优化可能是不需要的并且可能会扰乱我们的计时,或者您可能希望混合使用这两种方法。

除了潜在的次优 RAM 使用情况之外,我尝试了几个小时让编译器生成次优程序集,但我无法解决我的大多数玩具问题,因此看起来这确实是 Union 的一个相当漂亮的功能,而不是避开他们的理由。我最喜欢的 C++ 比喻是,C++ 就像一个装满锋利刀的厨房,你需要为正确的工作选择正确的刀,并且仅仅因为厨房里有很多锋利的刀并不意味着你把所有的刀都拿出来立即取出刀具,或者将刀具放在外面。只要保持厨房整洁,就不会割伤自己。Union 是一把锋利的刀,可以帮助确保更大的 RAM 一致性,但它需要更多的输入并减慢程序速度。