【问题标题】:performance overhead of the gettext internationalization system in C/C++C/C++ 中 gettext 国际化系统的性能开销
【发布时间】:2013-08-18 13:58:29
【问题描述】:

我刚刚浏览了http://www.gnu.org/software/gettext/manual/gettext.html 的文档,根本没有讨论性能开销。在互联网上,我发现只有其他语言(PHP 和 Java)的性能讨论,而没有发现 C/C++。

因此我的问题:

  1. 使用 gettext 的程序启动期间的性能开销是多少(加载共享库?翻译如何加载到内存中?所有翻译是在启动时加载还是按需加载?)

    李>
  2. 程序正常运行期间的性能损失是多少? (即需要翻译时)程序增加的内存占用有多大,内存是如何组织的?当程序空闲时,程序的某些部分被交换到磁盘是否存在更高的危险/可能性? (如果翻译存储在内存的与程序的其余部分完全不同的部分,那么据我所知,页面错误的机会比程序的非国际化版本更高)

  3. 在“C”语言环境下运行的程序是否也会遭受这些性能损失?

非常感谢。

【问题讨论】:

  • 一个使用gettext的程序正在生成人类可读的输出。开销小于人类阅读文本所需的开销,因此可以忽略不计。 (这并不完全正确,但实际上,开销不是问题。)
  • @JamesKanze 该程序可能还会生成冗长的报告,或者它可能会发送个性化的群发电子邮件,或者......输出是人类可读的并不意​​味着周围有人,并且程序可以停止,直到他/她读完输出。
  • @James:即使是导致页面错误的单个丢失字节也会导致延迟,即使是人类也可以注意到。在极端情况下,如果必须启动硬盘,可能会导致几秒钟的延迟。还有相当多的命令行程序是从脚本启动的,这可能意味着一个程序启动/停止了数千次。但真正的重点是“它可以忽略不计”并不是问题的答案,因为它只是在某些情况下可以忽略不计。
  • @James2:例如,如果我启动一个内存密集型任务(例如,如果你允许 git-operations 吃掉所有内存)并离开工作站,所有空闲程序都将被交换到磁盘。如果翻译存储在远离主程序的地方,即使主程序没有空闲并且在主内存中,当不需要翻译时,它们也会被交换到磁盘。然后,当我开始使用工作站时,数十甚至数百个进程会被交换回主内存 - 在这种情况下,可能需要几秒钟才能在主内存中使用翻译。

标签: c++ c performance gettext overhead


【解决方案1】:

鉴于这种方法的替代方案是进行大量构建,每个构建中都有类似的内容:

int main()
{
    printf(
#ifdef SWEDISH
           "Hej världen\n"
#elsif ENGLISH
           "Hello, World\n"
#elsif PORTUGUESE
           "Olá, Mundo\n"
#else  
   #error Language not specified. 
#endif
    );
    return 0l;
}

相反,我们得到:

int main()
{
   printf(gettext("Hello, World\n")); 
}

易于阅读和理解。

我不知道 gettext 实现的确切结构,但我希望它在加载后就是一个哈希表。可能是二叉树,但哈希表似乎更明智。

至于确切的开销,很难在上面加上一个数字 - 特别是,正如您所说,如果将某些东西交换到磁盘,并且磁盘已停止,则需要 3-4 秒才能使磁盘恢复正常速度。那么如何量化呢?是的,如果系统忙于执行内存密集型操作,gettext 所需的页面可能会被换出。

如果文件非常大,加载消息文件应该只是一个很大的开销,但同样,如果磁盘没有旋转,并且文件没有缓存,那么会有几秒钟的开销。再次,如何量化。文件的大小显然与翻译(或母语)消息的实际大小成正比。

关于第2点:

据我所知,在 Linux 和 Windows 中,页面都是根据“最近最少使用”(或其他一些使用统计)换出的,这与它们所在的位置无关。显然,翻译后的消息与实际代码位于不同的位置 - 源文件中没有 15 种不同翻译的列表,因此翻译是在运行时加载的,并且将位于与代码本身不同的位置。但是,这样的开销类似于以下之间的开销差异:

static const char *msg = "Hello, World\n";

static const char *msg = strdup("Hello, World\n"); 

鉴于文本字符串通常在程序的二进制文件中保存在一起,我认为它们与执行代码的“接近性”与堆中某处​​动态分配的内存没有显着差异。如果您经常调用gettext 函数,那么该内存将保持“最新”状态,不会被换出。如果您有一段时间不致电gettext,它可能会被换掉。但这适用于“最近没有使用存储在可执行文件中的字符串,所以它们被换掉了”。

3) 我认为英语(或“未选择语言”)被视为与任何其他语言变体完全相同。

我会再深入一点,先吃早餐……

非常不科学:

#include <libintl.h>
#include <cstdio>
#include <cstring>

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}


int main()
{
    char str[10000] = {};
    char *s = str;
    unsigned long long time;

    for(int i = 0; i < 10; i++)
    {
    time = rdtsc();
    s += sprintf(s, "Hello, World %d", i);
    time = rdtsc() - time;
    printf("Time =%lld\n", time);
    }
    printf("s = %s\n", str);
    s = str;

    strcpy(s, "");
    for(int i = 0; i < 10; i++)
    {
    time = rdtsc();
    s += sprintf(s, gettext("Hello, World %d"), i);
    time = rdtsc() - time;
    printf("Time =%lld\n", time);
    }
    printf("s = %s\n", str);
}

给出以下结果:

$ g++ -Wall -O2 intl.cpp
$ ./a.out
Time =138647
Time =9528
Time =6710
Time =5537
Time =5785
Time =5427
Time =5406
Time =5453
Time =5644
Time =5431
s = Hello, World 0Hello, World 1Hello, World 2Hello, World 3Hello, World 4Hello, World 5Hello, World 6Hello, World 7Hello, World 8Hello, World 9
Time =85965
Time =11929
Time =10123
Time =10226
Time =10628
Time =9613
Time =9515
Time =9336
Time =9440
Time =9095
s = Hello, World 0Hello, World 1Hello, World 2Hello, World 3Hello, World 4Hello, World 5Hello, World 6Hello, World 7Hello, World 8Hello, World 9

dcigettext.c 中的代码在平面字符串数组中混合使用二进制搜索,以及将字符串散列为 PJW 散列的散列函数(参见:http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html)。

因此,一旦应用程序启动,开销似乎在“非常明显”(计算时钟周期时),但不是很大。

在这两种情况下,运行第一个 sprintf 所需的确切时间有所不同,所以我不会说“使用 gettext”在第一次调用时使 sprintf 更快——只是在这次运行中“运气不好”(我有一些其他的代码变体,它们在第一次调用sprintf 时变化很大,以后调用时变化较小)。可能需要额外时间的某些设置(可能是缓存 [printf 导致缓存被其他垃圾覆盖很可能]、分支预测等)...

现在,这显然不能回答您关于分页等问题。而且我没有尝试对我的“Hello, World”消息进行瑞典语、葡萄牙语或德语翻译。我仍然相信它不是很大,除非您确实每秒运行 100 次应用程序的实例化,并且该应用程序除了在进行一些简单的计算后在屏幕上打印一条消息之外并没有做太多事情,当然,这可能很重要.

找出它有多大不同的唯一真正方法是用#define _(x) x而不是#define _(x) gettext(x)编译相同的应用程序,看看你是否注意到任何不同。

我仍然认为“分页”是一个红鲱鱼。如果机器处于高内存压力下,那么无论如何它都会运行缓慢(如果我在我的机器上编写一段分配 16GB [机器中有 16GB RAM] 的代码,几乎除了键盘本身之外的所有东西(可以闪烁数字锁定 LED)并且鼠标指针本身(可以在屏幕上移动鼠标指针)无响应)。

【讨论】:

  • 感谢您的回复;哈希表意味着AFAIK,对于每次翻译,必须在运行时创建一个哈希,并且必须在表中找到该哈希。在这种情况下,您的“Hello World”应用程序的运行速度可能会比没有 gettext 时慢几倍。
  • 关于第2点:它们的位置非常重要。当它们位于主程序附近时,两者在同一页面上的机会非常高!与仅使用一页的程序相比,分布在许多页面上的程序会导致更多的页面丢失。
  • 是的,但是要使文本与您的代码在同一页面上,代码需要非常小(小于 4KB)。如果我们谈论的一些代码实际上做了一些有意义和有用的事情,除了打印“Hello, World\n”,那么它的代码和文本很可能至少覆盖了不止一个页面。计算字符串的哈希值并不完全是微不足道的,但比 printf 对格式字符串的处理更简单,所以我不相信你就在那里。但是我已经吃过早餐了,现在看看 libintl 实际做了什么。
  • @Robby75:我已经用一个非常简单的测试用例更新了我的答案,即“调用gettext 实际提供了多少开销”。答案是“不能忽视它,但如果您的应用程序所做的不仅仅是打印几条消息,那么可能不是最大的罪魁祸首”。
【解决方案2】:

一些测量:

    for ( ; n > 0; n--) {
#ifdef I18N
            fputs(gettext("Greetings!"), stdout);
#else
            fputs("Greetings!", stdout);
#endif
            putc('\n', stdout);
    }

n = 10000000(1000 万),并将输出重定向到文件。语言环境没有 po 文件,因此会打印原始字符串(相同的输出文件)。用户时间(秒):

  • 0.23,I18N 未定义
  • 4.43 与 I18N
  • 2.33,I18N 和 LC_ALL=C

每次调用的开销为 0.4 微秒。 (在 Phenom X6 @3.6GHz、Fedora 19 上)。使用 LC_ALL=C 时,开销仅为 0.2 µs。请注意,这可能是最坏的情况——通常你会在你的程序中做更多的事情。尽管如此,它仍然是 20 倍,其中包括 IO。 gettext() 比我预期的要慢。

内存使用我没有测量,因为它可能取决于 po 文件的大小。启动时间我不知道如何衡量。

【讨论】:

  • 可能将 n 设置为 1 并在脚本中启动它:for i in `seq 10000000` ; do run_test_program; done 以测量启动时间。事实上,这种情况正是在现实世界中经常使用命令行工具完成的。
  • 另一件事:你说你没有 po-file,这意味着使用 po-file 的开销仍然会更大,因为它必须解析/搜索/等 - 所以更糟糕的最坏情况;-)
猜你喜欢
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
  • 2010-10-09
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
相关资源
最近更新 更多