我做了一个测试来比较几种语言的字符串操作,以便为服务器端应用程序选择一种语言。结果似乎很正常,直到我最终尝试了C ++,这让我感到非常惊讶。所以我想知道我是否错过了任何优化并来到这里寻求帮助。
测试主要是密集的字符串操作,包括连接和搜索。测试在Ubuntu 11.10 amd64上进行,GCC版本为4.6.1。该机器是戴尔Optiplex 960,配备4G RAM和四核CPU。
Python(2.7.2)中的def test():
x = ""
limit = 102 * 1024
while len(x) < limit:
x += "X"
if x.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) > 0:
print("Oh my god, this is impossible!")
print("x's length is : %d" % len(x))
test()
给出了结果:
x's length is : 104448
real 0m8.799s
user 0m8.769s
sys 0m0.008s
Java中的public class test {
public static void main(String[] args) {
int x = 0;
int limit = 102 * 1024;
String s="";
for (; s.length() < limit;) {
s += "X";
if (s.indexOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ") > 0)
System.out.printf("Find!\n");
}
System.out.printf("x's length = %d\n", s.length());
}
}
给出了结果:
x's length = 104448
real 0m50.436s
user 0m50.431s
sys 0m0.488s
function test()
{
var x = "";
var limit = 102 * 1024;
while (x.length < limit) {
x += "X";
if (x.indexOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) > 0)
console.log("OK");
}
console.log("x's length = " + x.length);
}();
给出了结果:
x's length = 104448
real 0m3.115s
user 0m3.084s
sys 0m0.048s
C ++中的Nodejs的性能优于Python或Java,这并不奇怪。但是我期望libstdc ++会比Nodejs提供更好的性能,其结果让我感到很惊讶。
#include <iostream>
#include <string>
using namespace std;
void test()
{
int x = 0;
int limit = 102 * 1024;
string s("");
for (; s.size() < limit;) {
s += "X";
if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != string::npos)
cout << "Find!" << endl;
}
cout << "x's length = " << s.size() << endl;
}
int main()
{
test();
}
给出了结果:
x length = 104448
real 0m5.905s
user 0m5.900s
sys 0m0.000s
好的,现在让我们看一下摘要:
令人惊奇!我在C ++中尝试了“-O2,-O3”但注意到了帮助。在V8中,C ++似乎只有50%的javascript性能,甚至比CPython差。任何人都可以向我解释我是否错过了海湾合作委员会的一些优化或者情况如何?非常感谢你。
答案 0 :(得分:71)
并不是std::string
表现不佳(尽管我不喜欢C ++),而是字符串处理对其他语言进行了大量优化。
你对字符串表现的比较是误导性的,如果它们的目的不仅仅是代表那么,那就是冒昧。
由于unicode字符串和字节之间缺乏分离,我知道Python string objects are completely implemented in C,实际上在Python 2.7上存在numerous optimizations。如果你在Python 3.x上运行这个测试,你会发现它相当慢。
Javascript有许多经过大量优化的实现。可以预期字符串处理非常好。
您的Java结果可能是由于不正确的字符串处理或其他一些不良情况造成的。我希望Java专家可以介入并通过一些更改来修复此测试。
至于你的C ++示例,我希望性能稍微超过Python版本。它执行相同的操作,减少了解释器开销。这反映在您的结果中。使用s.reserve(limit);
进行测试之前将删除重新分配开销。
我再说一遍,你只测试语言实现的一个方面。此测试的结果不能反映整体语言速度。
我提供了一个C版本来展示这样的小便竞赛有多么愚蠢:
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
void test()
{
int limit = 102 * 1024;
char s[limit];
size_t size = 0;
while (size < limit) {
s[size++] = 'X';
if (memmem(s, size, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26)) {
fprintf(stderr, "zomg\n");
return;
}
}
printf("x's length = %zu\n", size);
}
int main()
{
test();
return 0;
}
定时:
matt@stanley:~/Desktop$ time ./smash
x's length = 104448
real 0m0.681s
user 0m0.680s
sys 0m0.000s
答案 1 :(得分:34)
所以我在ideone.org上玩了一下这个。
这里是原始C ++程序的略微修改版本,但在循环中附加了消除,因此它只测量对std::string::find()
的调用。 请注意,我必须将迭代次数减少到~40%,否则ideone.org会终止这个过程。
#include <iostream>
#include <string>
int main()
{
const std::string::size_type limit = 42 * 1024;
unsigned int found = 0;
//std::string s;
std::string s(limit, 'X');
for (std::string::size_type i = 0; i < limit; ++i) {
//s += 'X';
if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != std::string::npos)
++found;
}
if(found > 0)
std::cout << "Found " << found << " times!\n";
std::cout << "x's length = " << s.size() << '\n';
return 0;
}
ideone.org的结果为time: 3.37s
。 (当然,这是非常值得怀疑的,但请放纵我一会儿,等待其他结果。)
现在我们采用此代码并交换注释行,以测试追加,而不是查找。 请注意,这一次,我尝试查看任何时间结果时,迭代次数增加了十倍。
#include <iostream>
#include <string>
int main()
{
const std::string::size_type limit = 1020 * 1024;
unsigned int found = 0;
std::string s;
//std::string s(limit, 'X');
for (std::string::size_type i = 0; i < limit; ++i) {
s += 'X';
//if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != std::string::npos)
// ++found;
}
if(found > 0)
std::cout << "Found " << found << " times!\n";
std::cout << "x's length = " << s.size() << '\n';
return 0;
}
尽管迭代次数增加了十倍,我在ideone.org的结果为time: 0s
。
我的结论:这个基准测试在C ++中, 高度受搜索操作 支配,循环中字符的追加对结果没有任何影响。这真的是你的意图吗?
答案 2 :(得分:14)
惯用的C ++解决方案是:
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
const int limit = 102 * 1024;
std::string s;
s.reserve(limit);
const std::string pattern("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
for (int i = 0; i < limit; ++i) {
s += 'X';
if (std::search(s.begin(), s.end(), pattern.begin(), pattern.end()) != s.end())
std::cout << "Omg Wtf found!";
}
std::cout << "X's length = " << s.size();
return 0;
}
我可以通过将字符串放在堆栈上并使用memmem来大大提高速度 - 但似乎没有必要。在我的机器上运行,这已经是python解决方案速度的10倍......
[在我的笔记本电脑上]
时间./test X的长度= 104448 真正的0m2.055s 用户0m2.049s sys 0m0.001s
答案 3 :(得分:8)
这是最明显的一个:请在主循环之前尝试s.reserve(limit);
。
文档为here。
我应该提一下,在C ++中直接使用标准类的方式与在Java或Python中使用它的方式相同,如果您不知道桌面背后的操作,通常会给您带来低于标准的性能。语言本身没有神奇的表现,它只是为你提供了正确的工具。
答案 4 :(得分:6)
您在这里缺少的是查找搜索的固有复杂性。
您正在执行搜索102 * 1024
(104 448)次。一个天真的搜索算法,每次都会尝试匹配从第一个字符开始,然后是第二个字符等的模式......
因此,您有一个从长度1
到N
的字符串,并且在每一步中您都会针对此字符串搜索模式,这是C ++中的线性操作。这是N * (N+1) / 2 = 5 454 744 576
比较。我并不像你那样惊讶,这需要一些时间......
让我们通过使用搜索单find
的{{1}}的重载来验证假设:
A
快3倍左右,所以我们处于同一数量级。因此,使用完整的字符串并不是很有趣。
结论?也许Original: 6.94938e+06 ms
Char : 2.10709e+06 ms
可以稍微优化一下。但问题不值得。
注意:对于那些吹嘘Boyer Moore的人,我担心针太小了,所以它无济于事。可能会减少一个数量级(26个字符),但不会更多。
答案 5 :(得分:5)
我的第一个想法是没有问题。
C ++提供了第二好的性能,比Java快了近十倍。也许除了Java之外的所有功能都接近该功能可实现的最佳性能,您应该考虑如何修复Java问题(提示 - StringBuilder
)。
在C ++的情况下,有一些事情要尝试提高性能。特别是......
s += 'X';
而不是s += "X";
string searchpattern ("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
,并将其传递给find
个调用。 std::string
实例知道它自己的长度,而C字符串需要线性时间检查来确定,并且这可能(或可能不)与std::string::find
性能相关。std::stringstream
,原因与您使用StringBuilder
for Java的原因相似,但很可能重复转换回string
会产生更多问题。总的来说,结果并不令人惊讶。使用良好的JIT编译器的JavaScript可能能够比在这种情况下允许C ++静态编译更好地优化。
有了足够的工作,你应该总是能够比JavaScript更好地优化C ++,但总会有这样的情况,这不仅仅是自然发生的,也可能需要相当多的知识和努力才能实现。< / p>
答案 6 :(得分:4)
对于C ++,尝试使用std::string
作为“ABCDEFGHIJKLMNOPQRSTUVWXYZ” - 在我的实现中string::find(const charT* s, size_type pos = 0) const
计算字符串参数的长度。
答案 7 :(得分:4)
C / C ++语言并不容易,需要数年才能制作出快速的程序。
使用从c版本修改的strncmp(3)版本:
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
void test()
{
int limit = 102 * 1024;
char s[limit];
size_t size = 0;
while (size < limit) {
s[size++] = 'X';
if (!strncmp(s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26)) {
fprintf(stderr, "zomg\n");
return;
}
}
printf("x's length = %zu\n", size);
}
int main()
{
test();
return 0;
}
答案 8 :(得分:3)
我自己刚刚测试了C ++示例。如果我删除了对std::sting::find
的调用,程序会立即终止。因此,字符串连接期间的分配在这里没有问题。
如果我在sdt::string abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
的调用中添加变量std::string::find
并替换“ABC ... XYZ”的出现,则程序需要几乎与原始示例完成相同的时间。这再次表明,分配以及计算字符串的长度不会给运行时增加太多。
因此,libstdc ++使用的字符串搜索算法似乎不如javascript或python的搜索算法那么快。也许你想用你自己的字符串搜索算法再次尝试C ++,这更适合你的目的。
答案 9 :(得分:1)
似乎在nodejs中搜索子串的算法更好。你可以实现自我并尝试它。
答案 10 :(得分:1)
您的测试代码正在检查过多字符串连接的病态场景。 (测试的字符串搜索部分可能已被省略,我敢打赌,它对最终结果几乎没有贡献。)过多的字符串连接是大多数语言强烈反对的缺陷,并提供了众所周知的替代方案, (即StringBuilder,)所以你在这里测试的是这些语言在完全预期失败的情况下失败的程度。这毫无意义。
类似的无意义测试的一个例子是在紧密循环中抛出和捕获异常时比较各种语言的性能。所有语言都警告异常抛出和捕获异常缓慢。他们没有说明有多慢,他们只是警告你不要期待任何事情。因此,继续前进并精确测试,将毫无意义。
因此,重复测试更有意义的是用无意义的字符串连接部分(s + =“X”)代替这些语言中的每一种提供的任何构造,以避免字符串连接。 (比如StringBuilder类。)
答案 11 :(得分:0)
如sbi所述,测试用例由搜索操作主导。 我很好奇文本分配在C ++和Javascript之间的比较速度。
系统:Raspberry Pi 2,g ++ 4.6.3,节点v0.12.0,g ++ -std = c ++ 0x -O2 perf.cpp
C ++:770ms
没有保留的C ++:1196ms
Javascript:2310ms
<强> C ++ 强>
array
<强>的JavaScript 强>
create or replace function extr( tabname text ) returns text[] as
$$
declare cols text[];
begin
select array(select column_name::text from information_schema.columns
where table_name = tabname) into cols;
return cols;
end;
$$
language 'plpgsql';