C++ C+中的对数+;和组装

C++ C+中的对数+;和组装,c++,performance,assembly,x86-64,logarithm,C++,Performance,Assembly,X86 64,Logarithm,显然MSVC++ 2017工具集V141(X64发布配置)不使用 FYL2X X86Y64汇编指令,而不是C++(代码>日志)(或代码>或 Log2()>代码>用法,导致对长函数的真实调用,这似乎是对数近似的实现(不使用 FYL2X )。我测量的性能也很奇怪:log()(自然对数)比log2()(以2为底的对数)快1.7667倍,尽管以2为底的对数对于处理器来说应该更容易,因为它以二进制格式存储指数(以及尾数),这就是为什么CPU指令FYL2X计算以2为底的对数(乘以一个参数) 以下是用于测量

显然MSVC++ 2017工具集V141(X64发布配置)不使用<代码> FYL2X X86Y64汇编指令,而不是C++(代码>日志)(或代码>或<代码> Log2()>代码>用法,导致对长函数的真实调用,这似乎是对数近似的实现(不使用<代码> FYL2X )。我测量的性能也很奇怪:
log()
(自然对数)比
log2()
(以2为底的对数)快1.7667倍,尽管以2为底的对数对于处理器来说应该更容易,因为它以二进制格式存储指数(以及尾数),这就是为什么CPU指令
FYL2X
计算以2为底的对数(乘以一个参数)

以下是用于测量的代码:

#include <chrono>
#include <cmath>
#include <cstdio>

const int64_t cnLogs = 100 * 1000 * 1000;

void BenchmarkLog2() {
  double sum = 0;
  auto start = std::chrono::high_resolution_clock::now();
  for(int64_t i=1; i<=cnLogs; i++) {
    sum += std::log2(double(i));
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Log2: %.3lf Ops/sec calculated %.3lf\n", cnLogs / nSec, sum);
}

void BenchmarkLn() {
  double sum = 0;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 1; i <= cnLogs; i++) {
    sum += std::log(double(i));
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Ln: %.3lf Ops/sec calculated %.3lf\n", cnLogs / nSec, sum);
}

int main() {
    BenchmarkLog2();
    BenchmarkLn();
    return 0;
}
因此,为了阐明这些现象(没有使用
FYL2X
和奇怪的性能差异),我还想测试
FYL2X
的性能,如果它更快,请使用它而不是
的函数。MSVC++不允许在x64上进行内联汇编,因此需要使用
FYL2X
的汇编文件函数


如果在较新的x86_64处理器上有这样一个函数,您能用使用
FYL2X
的汇编代码或更好的对数指令(不需要特定的基数)来回答吗?

下面是使用
FYL2X
的汇编代码:

_DATA SEGMENT

_DATA ENDS

_TEXT SEGMENT

PUBLIC SRLog2MulD

; XMM0L=toLog
; XMM1L=toMul
SRLog2MulD PROC
  movq qword ptr [rsp+16], xmm1
  movq qword ptr [rsp+8], xmm0
  fld qword ptr [rsp+16]
  fld qword ptr [rsp+8]
  fyl2x
  fstp qword ptr [rsp+8]
  movq xmm0, qword ptr [rsp+8]
  ret

SRLog2MulD ENDP

_TEXT ENDS

END
呼叫约定根据,例如

x87寄存器堆栈未使用。被调用方可以使用它,但是 必须考虑跨函数调用的易失性

< C++中的原型是:

extern "C" double __fastcall SRLog2MulD(const double toLog, const double toMul);
性能比
std::log2()
慢2倍,比
std::log()慢3倍以上:

基准代码如下所示:

void BenchmarkFpuLog2() {
  double sum = 0;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 1; i <= cnLogs; i++) {
    sum += SRPlat::SRLog2MulD(double(i), 1);
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("FPU Log2: %.3lf Ops/sec calculated %.3lf\n", cnLogs / nSec, sum);
}
void BenchmarkFpuLog2(){
双和=0;
自动启动=标准::时钟::高分辨率时钟::现在();

对于(In64)t i=1;我在寻找什么样的精度/速度折衷?<代码> FYL2X 不快,虽然RyZin不像现代英特尔那样坏。@哈罗德,我希望CPU提供的准确性/速度,即没有软件近似。如果硬件中有多个选项,我想考虑一下。所有.Log在不同的基中通常是通过将结果乘以一个常数(
Log_n(x)=log2(x)*Log_n(2)
)来完成的。或者只是将其烘焙到多项式近似的常数中,用于
Log(尾数)
。例如,请参见Agner Fog的高精度
Log(\u m256d)的向量类库实现
和其他基础的版本。(单精度版本稍微简单一些,不需要那么大的多项式来获得接近1 ulp的精度)。为了更快地逼近SSE2 log2(),请参阅。AVX2或AVX512版本的多项式使用FMA是非常有效的,即使使用5阶多项式也非常精确。在现代CPU上使用
FYL2X
是没有意义的,除非您优化的是代码大小,而不是速度。@PeterCordes我尝试了一些东西,发现二阶多项式的比率多项式可以比五阶多项式更好地拟合一个数量级,因此以除法为代价节省了3个FMA的延迟,有时可能有用您测试的CPU是什么?我不认为有任何现代CPU的x87
FYL2X
速度比精确的SSE2库实现快。不过,这样的速度太慢了ess超出了额外的存储/重新加载开销(用于在xmm和x87寄存器之间移动数据)。@PeterCordes,CPU在库存时钟上为AMD Ryzen 1800X。
Log2: 94803174.389 Ops/sec calculated 2513272986.435
FPU Log2: 52008300.525 Ops/sec calculated 2513272986.435
Ln: 169392473.892 Ops/sec calculated 1742068084.525
void BenchmarkFpuLog2() {
  double sum = 0;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 1; i <= cnLogs; i++) {
    sum += SRPlat::SRLog2MulD(double(i), 1);
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("FPU Log2: %.3lf Ops/sec calculated %.3lf\n", cnLogs / nSec, sum);
}