cvCeil() 比标准库快吗?
is cvCeil() faster than standard library?
我看到 OpenCV 实现了 cvCeil
功能:
CV_INLINE int cvCeil( double value )
{
#if defined _MSC_VER && defined _M_X64 || (defined __GNUC__ && defined __SSE2__&& !defined __APPLE__)
__m128d t = _mm_set_sd( value );
int i = _mm_cvtsd_si32(t);
return i + _mm_movemask_pd(_mm_cmplt_sd(_mm_cvtsi32_sd(t,i), t));
#elif defined __GNUC__
int i = (int)value;
return i + (i < value);
#else
int i = cvRound(value);
float diff = (float)(i - value);
return i + (diff < 0);
#endif
}
我对这个实现的第一部分很好奇,即 _mm_set_sd
相关调用。它们会比 MSVCRT / libstdc++ / libc++ 更快吗?为什么?
下面的一个简单基准告诉我,std::round
在启用 SSE4 的机器上运行速度提高了 3 倍以上,但在未启用 SSE4 时速度降低了大约 2 倍。
#include <cmath>
#include <chrono>
#include <sstream>
#include <iostream>
#include <opencv2/core/fast_math.hpp>
auto currentTime() { return std::chrono::steady_clock::now(); }
template<typename T, typename P>
std::string toString(std::chrono::duration<T,P> dt)
{
std::ostringstream str;
using namespace std::chrono;
str << duration_cast<microseconds>(dt).count()*1e-3 << " ms";
return str.str();
}
int main()
{
volatile double x=34.234;
volatile double y;
constexpr auto MAX_ITER=100'000'000;
const auto t0=currentTime();
for(int i=0;i<MAX_ITER;++i)
y=std::ceil(x);
const auto t1=currentTime();
for(int i=0;i<MAX_ITER;++i)
y=cvCeil(x);
const auto t2=currentTime();
std::cout << "std::ceil: " << toString(t1-t0) << "\n"
"cvCeil : " << toString(t2-t1) << "\n";
}
我在 GCC 8.3.0、glibc-2.27、Ubuntu 18.04.1 x86_64 Intel Core i7-3930K 3.2 GHz 上使用 -O3
选项进行测试。
使用-msse4
编译时的输出:
std::ceil: 39.357 ms
cvCeil : 143.224 ms
没有-msse4
编译时的输出:
std::ceil: 274.945 ms
cvCeil : 146.218 ms
很容易理解:SSE4.1引入了ROUNDSD
指令,基本上就是std::round
的作用。在此之前,编译器必须做一些 comparison/conditional-moves 技巧,并且还必须确保这些技巧不会溢出。因此,cvCeil
版本牺牲了 value>INT_MAX
和 value<INT_MIN
的明确定义性,获得了明确定义的值的加速。对于其他人,它具有未定义的行为(或者,对于内在函数,只会给出错误的结果)。
我看到 OpenCV 实现了 cvCeil
功能:
CV_INLINE int cvCeil( double value )
{
#if defined _MSC_VER && defined _M_X64 || (defined __GNUC__ && defined __SSE2__&& !defined __APPLE__)
__m128d t = _mm_set_sd( value );
int i = _mm_cvtsd_si32(t);
return i + _mm_movemask_pd(_mm_cmplt_sd(_mm_cvtsi32_sd(t,i), t));
#elif defined __GNUC__
int i = (int)value;
return i + (i < value);
#else
int i = cvRound(value);
float diff = (float)(i - value);
return i + (diff < 0);
#endif
}
我对这个实现的第一部分很好奇,即 _mm_set_sd
相关调用。它们会比 MSVCRT / libstdc++ / libc++ 更快吗?为什么?
下面的一个简单基准告诉我,std::round
在启用 SSE4 的机器上运行速度提高了 3 倍以上,但在未启用 SSE4 时速度降低了大约 2 倍。
#include <cmath>
#include <chrono>
#include <sstream>
#include <iostream>
#include <opencv2/core/fast_math.hpp>
auto currentTime() { return std::chrono::steady_clock::now(); }
template<typename T, typename P>
std::string toString(std::chrono::duration<T,P> dt)
{
std::ostringstream str;
using namespace std::chrono;
str << duration_cast<microseconds>(dt).count()*1e-3 << " ms";
return str.str();
}
int main()
{
volatile double x=34.234;
volatile double y;
constexpr auto MAX_ITER=100'000'000;
const auto t0=currentTime();
for(int i=0;i<MAX_ITER;++i)
y=std::ceil(x);
const auto t1=currentTime();
for(int i=0;i<MAX_ITER;++i)
y=cvCeil(x);
const auto t2=currentTime();
std::cout << "std::ceil: " << toString(t1-t0) << "\n"
"cvCeil : " << toString(t2-t1) << "\n";
}
我在 GCC 8.3.0、glibc-2.27、Ubuntu 18.04.1 x86_64 Intel Core i7-3930K 3.2 GHz 上使用 -O3
选项进行测试。
使用-msse4
编译时的输出:
std::ceil: 39.357 ms
cvCeil : 143.224 ms
没有-msse4
编译时的输出:
std::ceil: 274.945 ms
cvCeil : 146.218 ms
很容易理解:SSE4.1引入了ROUNDSD
指令,基本上就是std::round
的作用。在此之前,编译器必须做一些 comparison/conditional-moves 技巧,并且还必须确保这些技巧不会溢出。因此,cvCeil
版本牺牲了 value>INT_MAX
和 value<INT_MIN
的明确定义性,获得了明确定义的值的加速。对于其他人,它具有未定义的行为(或者,对于内在函数,只会给出错误的结果)。