Mat*_*t B 5 c linux windows performance
我正在诊断跨平台(Windows和Linux)应用程序中的边缘情况,其中toupper在Windows上要慢得多.我假设这对于tolower来说也是一样的.
最初我用一个简单的C程序测试了它,每个没有语言环境信息集,甚至包括头文件,性能差异很小.测试是一个百万次迭代循环,将字符串的每个字符调用到toupper()函数.
包含头文件并包含下面的行之后,它的速度要慢很多,并调用了很多MS C运行时库语言环境特定的函数.这很好,但性能受到了很大影响.在Linux上,这似乎对性能没有任何影响.
setlocale(LC_ALL, ""); // system default locale
Run Code Online (Sandbox Code Playgroud)
如果我设置以下它运行速度与linux一样快,但似乎跳过所有语言环境功能.
setlocale(LC_ALL, NULL); // should be interpreted as the same as below?
OR
setlocale(LC_ALL, "C");
Run Code Online (Sandbox Code Playgroud)
注意:Visual Studio 2015 for Windows 10 G ++ for Linux运行Cent OS
尝试过荷兰设置设置和相同的结果,在Windows上没有速度差在Linux上慢.
我是不是做错了什么或有与区域设置错误在Windows还是Linux的地方是没有做它应该在其他的方式?我还没有对linux应用程序进行调试,因为我对linux并不熟悉,所以不知道它在内部做了什么.接下来我应该测试什么才能解决这个问题?
下面的代码用于测试(Linux):
// C++ is only used for timing. The original program is in C.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <chrono>
#include <locale.h>
using namespace std::chrono;
void strToUpper(char *strVal);
int main()
{
typedef high_resolution_clock Clock;
high_resolution_clock::time_point t1 = Clock::now();
// set locale
//setlocale(LC_ALL,"nl_NL");
setlocale(LC_ALL,"en_US");
// testing string
char str[] = "the quick brown fox jumps over the lazy dog";
for (int i = 0; i < 1000000; i++)
{
strToUpper(str);
}
high_resolution_clock::time_point t2 = Clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
printf("chrono time %2.6f:\n",time_span.count());
}
void strToUpper(char *strVal)
{
unsigned char *t;
t = (unsigned char *)strVal;
while (*t)
{
*t = toupper(*t);
*t++;
}
}
Run Code Online (Sandbox Code Playgroud)
对于Windows,将本地信息更改为:
// set locale
//setlocale(LC_ALL,"nld_nld");
setlocale(LC_ALL, "english_us");
Run Code Online (Sandbox Code Playgroud)
您可以在完成的时间内看到分隔符的区域设置更改,完全停止与逗号.
编辑 - 分析数据
正如您在上面看到的,大部分时间都来自_toupper_l的子系统调用.没有语言环境信息设置,toupper调用不会调用子_toupper_l,这使得它非常快.
Linux 使用的 glibc 实现预计 LANG=C 与 LANG=any else 具有相同(且相当不错)的性能。
你的 Linux 结果是有道理的。 你的测试方法应该没问题。使用分析器查看微基准测试在 Windows 函数中花费了多少时间。如果 Windows 实现确实是问题所在,也许有一个 Windows 函数可以转换整个字符串,就像 C++ 一样boost::to_upper_copy<std::string>
(除非速度更慢,请参见下文)。
另请注意,大写 ASCII 字符串可以非常有效地进行 SIMD 矢量化。我在另一个答案中使用 C SSE 内在函数为单个向量编写了一个大小写翻转函数;它可以适应大写而不是翻盖。如果您花费大量时间对长度超过 16 个字节且您知道是 ASCII 的字符串进行升写,这应该会带来巨大的加速。
实际上,Boost 的to_upper_copy() 似乎编译成极其缓慢的代码,比toupper
. 请参阅我的矢量化链接strtoupper(dst,src)
,该链接仅支持 ASCII,但在检测到非 ASCII src 字节时可以通过回退进行扩展。
您当前的代码如何处理 UTF-8?如果您假设所有字符都是单个字节,那么支持非 ASCII 语言环境并没有多大好处。IIRC,Windows 对大多数内容使用 UTF-16,这很不幸,因为事实证明世界需要超过 2^16 的代码点。UTF-16 是 Unicode 的变长编码,类似于 UTF-8,但没有读取 ASCII 的优点。固定宽度有很多优势,但不幸的是,即使使用 UTF-16,你也不能假设这一点。Java 也犯了这个错误,并被 UTF-16 困住了。
glibc 源码是:
#define __ctype_toupper \
((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
int toupper (int c) {
return c >= -128 && c < 256 ? __ctype_toupper[c] : c;
}
Run Code Online (Sandbox Code Playgroud)
x86-64 Ubuntu 15.10 的 asm/lib/x86_64-linux-gnu/libc.so.6
是:
## disassembly from objconv -fyasm -v2 /lib/x86_64-linux-gnu/libc.so.6 /dev/stdout 2>&1
toupper:
lea edx, [rdi+80H] ; 0002E300 _ 8D. 97, 00000080
movsxd rax, edi ; 0002E306 _ 48: 63. C7
cmp edx, 383 ; 0002E309 _ 81. FA, 0000017F
ja ?_01766 ; 0002E30F _ 77, 19
mov rdx, qword [rel ?_37923] ; 0002E311 _ 48: 8B. 15, 00395AA8(rel)
sub rax, -128 ; 0002E318 _ 48: 83. E8, 80
mov rdx, qword [fs:rdx] ; 0002E31C _ 64 48: 8B. 12
mov rdx, qword [rdx] ; 0002E320 _ 48: 8B. 12
mov rdx, qword [rdx+48H] ; 0002E323 _ 48: 8B. 52, 48
mov eax, dword [rdx+rax*4] ; 0002E327 _ 8B. 04 82 ## the final table lookup, indexing an array of 4B ints
?_01766:
rep ret ; actual objconv output shows the prefix on a separate line
Run Code Online (Sandbox Code Playgroud)
因此,如果 arg 不在 0 - 0xFF 范围内,则需要提前退出(因此该分支应该完全预测不被采用),否则它会找到当前语言环境的表,这涉及三个指针取消引用:一次加载一个是全局的,一个是线程局部的,还有一个是取消引用。然后它实际上索引到 256 个条目的表中。
这是整个库函数;toupper
反汇编中的标签是您的代码所调用的。(好吧,由于动态链接,通过 PLT 进行了一层间接,但是在第一次调用触发惰性符号查找之后,它只是jmp
您的代码和库中的 11 个 insn 之间的一条额外指令。)