c - 设置语言环境时 Windows C 运行时 toupper 变慢

标签 c linux windows performance

我正在诊断跨平台(Windows 和 Linux)应用程序中的边缘情况,其中 toupper 在 Windows 上的速度要慢得多。我假设这对于 tolower 也是一样的。

最初我用一个简单的 C 程序对每个程序进行了测试,没有设置语言环境信息,甚至没有包括头文件,并且性能差异很小。测试是一百万次迭代循环,将字符串的每个字符调用到 toupper() 函数。

在包含头文件和包含下面的行之后,速度要慢得多,并且会调用很多 MS C 运行时库区域设置特定函数。这很好,但对性能的影响真的很糟糕。在 Linux 上,这似乎对性能没有任何影响。

setlocale(LC_ALL, ""); // system default locale

如果我设置以下设置,它的运行速度与 linux 一样快,但似乎会跳过所有语言环境功能。

setlocale(LC_ALL, NULL); // should be interpreted as the same as below?
OR
setlocale(LC_ALL, "C"); 

注意: 适用于 Windows 10 的 Visual Studio 2015 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++;
    }
}

对于 Windows,将本地信息更改为:

// set locale
//setlocale(LC_ALL,"nld_nld");
setlocale(LC_ALL, "english_us");

您可以在完成时间(句点与逗号)中的分隔符中看到语言环境的变化。

编辑 - 分析数据 application function calls profiling 正如您在上面看到的,大部分时间花在来自 _toupper_l 的子系统调用上。 如果没有设置语言环境信息,toupper 调用不会调用子 _toupper_l,这使得它非常快。

最佳答案

对于 Linux 使用的 glibc 实现,LANG=C 与 LANG=anything 的性能相同(并且相当好)。

您的 Linux 结果有意义。您的测试方法可能没问题。使用探查器查看您的微基准测试在 Windows 函数中花费了多少时间。如果 Windows 实现确实是问题所在,也许有一个 Windows 函数可以转换整个字符串,例如 C++ boost::to_upper_copy<std::string> (除非那更慢,见下文)。


另请注意,大写 ASCII 字符串可以非常有效地进行 SIMD 向量化。我为单个 vector 写了一个 case-flip 函数 in another answer , 使用 C SSE 内在函数;它可以适应大写而不是翻盖。如果您花费大量时间对长度超过 16 字节且您知道是 ASCII 的字符串进行 upcasing,这应该是一个巨大的加速。

实际上,Boost 的 to_upper_copy() appears to compile to extremely slow code, like 10x slower than 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。


The glibc source是:

#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;
}

来自 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

因此,如果 arg 不在 0 - 0xFF 范围内(因此该分支应该预测完全未被采用),它会提前退出,否则它会找到当前语言环境的表,这涉及三个指针取消引用:一种来自全局,一种来自线程局部,还有一种取消引用。然后它实际上索引到包含 256 个条目的表中。

这是整个库函数; toupper反汇编中的标签是您的代码调用的内容。 (好吧,由于动态链接,通过 PLT 的一层间接,但在第一次调用触发惰性符号查找后,它只是你的代码和库中的那 11 个 insn 之间的一个额外的 jmp 指令。)

关于c - 设置语言环境时 Windows C 运行时 toupper 变慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36686381/

相关文章:

linux - 修改socket结构将数据包发送到另一个IP

windows - 从 powershell 访问卷影复制 (VSS) 快照

windows - 破坏 PS 控制台的简单 PowerShell 脚本

windows - 选择 'Run as administrator' 后,网络共享上的批处理文件立即关闭

c++ - 如何让一个整数对数组按两个整数排序,同时仍然在 C 中关联它们?

c++ - 为什么没有任何标准的 C 和/或 C++ 语言网关接口(interface)作为 Python 的 WSGI?

c - 当在另一个文件中声明结构时,如何使用结构作为函数的参数?

c++ - "old"C 特性可能比新的 C++ 特性更好的情况?

php - 如何在 Linux Fedora 上的 Apache 中启用 PHP?

c++ - 在 Linux 中获取 TCP/UDP 表