isdigit() 在 C++ 中不应该更快吗?
Shouldn't isdigit() be faster in c++?
我在 C++ 中使用 isdigit()
函数,但我发现它很慢,所以我实现了自己的 is_digit()
,请参阅下面的代码:
#include<iostream>
#include<cctype>
#include<ctime>
using namespace std;
static inline bool is_digit(char c)
{
return c>='0'&&c<='9';
}
int main()
{
char c='8';
time_t t1=clock(),t2,t3;
for(int i=0;i<1e9;i++)
is_digit(c);
t2=clock();
for(int i=0;i<1e9;i++)
isdigit(c);
t3=clock();
cout<<"is_digit:"<<(t2-t1)/CLOCKS_PER_SEC<<"\nisdigit:"<<(t3-t2)/CLOCKS_PER_SEC<<endl;
return 0;
}
经过运行,is_digit()
只用了1秒(1161ms),而isdigit()
却用了4秒(3674ms),我知道isdigit
是位实现的操作,isdigit()
不应该比is_digit()
快吗?
更新1
我使用带有默认选项的 MS VS2010,发布版本,如何使 isdigit()
比 VS 中的 is_digit()
更快?
update2
谢谢大家。
在 VS 中处于发布模式时,项目将针对速度默认值 (-O2).
进行优化
全部处于发布模式。
VS2010:
is_digit:1182(毫秒)
isdigit:3724(毫秒)
VS2013:
is_digit:0(毫秒)
isdigit:3806(毫秒)
带有 g++(4.7.1) 和 -O3 的代码块:
is_digit:1275(毫秒)
isdigit:1331(毫秒)
所以这是结论:
is_digit()
在 VS 中比 isdigit()
快,但在 g++ 中比 isdigit()
慢。
并且 g++ 中的 isdigit()
比 VS 中的 isdigit()
快。
所以 "VS sucks" 表现?
使用 -O3 查看此代码(适用于 g++)
#include<iostream>
#include<cctype>
#include<ctime>
#include <time.h>
#include <sys/time.h>
using namespace std;
static inline bool is_digit(char c)
{
return c>='0'&&c<='9';
}
int main()
{
char c='8';
struct timeval tvSt, tvEn;
time_t t1=clock(),t2,t3;
gettimeofday(&tvSt, 0);
for(int i=0;i<1e9;i++)
is_digit(c);
gettimeofday(&tvEn, 0);
cout << "is_digit:" << (tvEn.tv_sec - tvSt.tv_sec)*1000000 + (tvEn.tv_usec - tvSt.tv_usec) << " us"<< endl;
gettimeofday(&tvSt, 0);
for(int i=0;i<1e9;i++)
isdigit(c);
gettimeofday(&tvEn, 0);
cout << "isdigit:" << (tvEn.tv_sec - tvSt.tv_sec)*1000000 + (tvEn.tv_usec - tvSt.tv_usec) << " us"<< endl;
return 0;
}
结果:
is_digit:1610771 us
isdigit:1055976 us
所以,C++ 实现胜过你。
通常,当您衡量性能时,以秒为单位并不是一个好主意。至少考虑微秒级别。
我不确定 VS。请找出微秒级时钟并进行测量。
PS。请参考 https://msdn.microsoft.com/en-us/library/19z1t1wy.aspx VS 优化
在 clang/llvm [我选择的编译器] 中,isdigit
和 is_digit
将变成完全相同的代码,因为它针对特定的库调用进行了优化以翻译它进入 ((unsigned)(c-48) < 10u)
。
return c>='0' && c <='9';
也通过优化转换为 c-48 > 10
(作为编译器执行的通用 if x >= N && x <= M
-> x-N > (M-N)
转换)。
因此,理论上,这两个循环应该变成相同的代码(至少对于具有针对 isdigit
的此类优化的编译器 - 无论 MSVC 是否这样做,我都不能说,因为源代码对一般人来说是不可用的 public)。我知道gcc有类似的优化库调用的代码,但是目前我的机器上没有gcc源码,懒得去查[以我的经验,会难一点无论如何,比 llvm 代码更容易阅读。
llvm 中的代码:
Value *LibCallSimplifier::optimizeIsDigit(CallInst *CI, IRBuilder<> &B) {
Function *Callee = CI->getCalledFunction();
FunctionType *FT = Callee->getFunctionType();
// We require integer(i32)
if (FT->getNumParams() != 1 || !FT->getReturnType()->isIntegerTy() ||
!FT->getParamType(0)->isIntegerTy(32))
return nullptr;
// isdigit(c) -> (c-'0') <u 10
Value *Op = CI->getArgOperand(0);
Op = B.CreateSub(Op, B.getInt32('0'), "isdigittmp");
Op = B.CreateICmpULT(Op, B.getInt32(10), "isdigit");
return B.CreateZExt(Op, CI->getType());
}
对于那些不熟悉 LLVM 代码的人:它首先检查函数调用是否具有正确的参数数量和参数类型。如果失败,它 returns NULL 表示 "I can't optimise this"。否则,它会构建操作链来执行 if (c - '0' > 10)
使用无符号比较来处理 "negative" 值 [无符号是巨大的值]。
这样做会出错:
bool isdigit(int x)
{
return image_contains_finger(imagefiles[x]);
}
[但是用你自己的版本替换库函数通常会产生有趣的效果!]
您的函数 is_digit 可以通过以下方式更快地实现:
#define ISDIGIT(X) (((uint32_t)X - '0') < 10u)
保存一个比较的地方。我认为,这是 gcc 中的正常方法,但在 Microsoft Visual Studio 中我猜你有 isdigit() 的本地化版本(因此检查语言环境需要很长时间)。
我在 C++ 中使用 isdigit()
函数,但我发现它很慢,所以我实现了自己的 is_digit()
,请参阅下面的代码:
#include<iostream>
#include<cctype>
#include<ctime>
using namespace std;
static inline bool is_digit(char c)
{
return c>='0'&&c<='9';
}
int main()
{
char c='8';
time_t t1=clock(),t2,t3;
for(int i=0;i<1e9;i++)
is_digit(c);
t2=clock();
for(int i=0;i<1e9;i++)
isdigit(c);
t3=clock();
cout<<"is_digit:"<<(t2-t1)/CLOCKS_PER_SEC<<"\nisdigit:"<<(t3-t2)/CLOCKS_PER_SEC<<endl;
return 0;
}
经过运行,is_digit()
只用了1秒(1161ms),而isdigit()
却用了4秒(3674ms),我知道isdigit
是位实现的操作,isdigit()
不应该比is_digit()
快吗?
更新1
我使用带有默认选项的 MS VS2010,发布版本,如何使 isdigit()
比 VS 中的 is_digit()
更快?
update2
谢谢大家。 在 VS 中处于发布模式时,项目将针对速度默认值 (-O2).
进行优化全部处于发布模式。
VS2010: is_digit:1182(毫秒) isdigit:3724(毫秒)
VS2013: is_digit:0(毫秒) isdigit:3806(毫秒)
带有 g++(4.7.1) 和 -O3 的代码块: is_digit:1275(毫秒) isdigit:1331(毫秒)
所以这是结论:
is_digit()
在 VS 中比 isdigit()
快,但在 g++ 中比 isdigit()
慢。
并且 g++ 中的 isdigit()
比 VS 中的 isdigit()
快。
所以 "VS sucks" 表现?
使用 -O3 查看此代码(适用于 g++)
#include<iostream>
#include<cctype>
#include<ctime>
#include <time.h>
#include <sys/time.h>
using namespace std;
static inline bool is_digit(char c)
{
return c>='0'&&c<='9';
}
int main()
{
char c='8';
struct timeval tvSt, tvEn;
time_t t1=clock(),t2,t3;
gettimeofday(&tvSt, 0);
for(int i=0;i<1e9;i++)
is_digit(c);
gettimeofday(&tvEn, 0);
cout << "is_digit:" << (tvEn.tv_sec - tvSt.tv_sec)*1000000 + (tvEn.tv_usec - tvSt.tv_usec) << " us"<< endl;
gettimeofday(&tvSt, 0);
for(int i=0;i<1e9;i++)
isdigit(c);
gettimeofday(&tvEn, 0);
cout << "isdigit:" << (tvEn.tv_sec - tvSt.tv_sec)*1000000 + (tvEn.tv_usec - tvSt.tv_usec) << " us"<< endl;
return 0;
}
结果:
is_digit:1610771 us
isdigit:1055976 us
所以,C++ 实现胜过你。
通常,当您衡量性能时,以秒为单位并不是一个好主意。至少考虑微秒级别。
我不确定 VS。请找出微秒级时钟并进行测量。
PS。请参考 https://msdn.microsoft.com/en-us/library/19z1t1wy.aspx VS 优化
在 clang/llvm [我选择的编译器] 中,isdigit
和 is_digit
将变成完全相同的代码,因为它针对特定的库调用进行了优化以翻译它进入 ((unsigned)(c-48) < 10u)
。
return c>='0' && c <='9';
也通过优化转换为 c-48 > 10
(作为编译器执行的通用 if x >= N && x <= M
-> x-N > (M-N)
转换)。
因此,理论上,这两个循环应该变成相同的代码(至少对于具有针对 isdigit
的此类优化的编译器 - 无论 MSVC 是否这样做,我都不能说,因为源代码对一般人来说是不可用的 public)。我知道gcc有类似的优化库调用的代码,但是目前我的机器上没有gcc源码,懒得去查[以我的经验,会难一点无论如何,比 llvm 代码更容易阅读。
llvm 中的代码:
Value *LibCallSimplifier::optimizeIsDigit(CallInst *CI, IRBuilder<> &B) {
Function *Callee = CI->getCalledFunction();
FunctionType *FT = Callee->getFunctionType();
// We require integer(i32)
if (FT->getNumParams() != 1 || !FT->getReturnType()->isIntegerTy() ||
!FT->getParamType(0)->isIntegerTy(32))
return nullptr;
// isdigit(c) -> (c-'0') <u 10
Value *Op = CI->getArgOperand(0);
Op = B.CreateSub(Op, B.getInt32('0'), "isdigittmp");
Op = B.CreateICmpULT(Op, B.getInt32(10), "isdigit");
return B.CreateZExt(Op, CI->getType());
}
对于那些不熟悉 LLVM 代码的人:它首先检查函数调用是否具有正确的参数数量和参数类型。如果失败,它 returns NULL 表示 "I can't optimise this"。否则,它会构建操作链来执行 if (c - '0' > 10)
使用无符号比较来处理 "negative" 值 [无符号是巨大的值]。
这样做会出错:
bool isdigit(int x)
{
return image_contains_finger(imagefiles[x]);
}
[但是用你自己的版本替换库函数通常会产生有趣的效果!]
您的函数 is_digit 可以通过以下方式更快地实现:
#define ISDIGIT(X) (((uint32_t)X - '0') < 10u)
保存一个比较的地方。我认为,这是 gcc 中的正常方法,但在 Microsoft Visual Studio 中我猜你有 isdigit() 的本地化版本(因此检查语言环境需要很长时间)。