为什么在这种情况下,Python比C++更快.下面给出了
Why is Python faster than C++ in this case?
给出了Python和C++中的程序,它执行以下任务:从stdin中读取以空格分隔的单词,打印按字符串长度排序的唯一单词以及每个唯一单词的计数到stdout。输出行的格式为:长度、计数、单词。
例如,使用此输入文件(同义词库的 488kB(http://pastebin.com/raw.php?i=NeUBQ22T
带有格式的输出是这样的:
1 57 "
1 1 n
1 1 )
1 3 *
1 18 ,
1 7 -
1 1 R
1 13 .
1 2 1
1 1 S
1 5 2
1 1 3
1 2 4
1 2 &
1 91 %
1 1 5
1 1 6
1 1 7
1 1 8
1 2 9
1 16 ;
1 2 =
1 5 A
1 1 C
1 5 e
1 3 E
1 1 G
1 11 I
1 1 L
1 4 N
1 681 a
1 2 y
1 1 P
2 1 67
2 1 y;
2 1 P-
2 85 no
2 9 ne
2 779 of
2 1 n;
...
这是C++中的程序
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include <map>
bool compare_strlen (const std::string &lhs, const std::string &rhs) {
return (lhs.length() < rhs.length());
}
int main (int argc, char *argv[]) {
std::string str;
std::vector<std::string> words;
/* Extract words from the input file, splitting on whitespace */
while (std::cin >> str) {
words.push_back(str);
}
/* Extract unique words and count the number of occurances of each word */
std::set<std::string> unique_words;
std::map<std::string,int> word_count;
for (std::vector<std::string>::iterator it = words.begin();
it != words.end(); ++it) {
unique_words.insert(*it);
word_count[*it]++;
}
words.clear();
std::copy(unique_words.begin(), unique_words.end(),
std::back_inserter(words));
// Sort by word length
std::sort(words.begin(), words.end(), compare_strlen);
// Print words with length and number of occurances
for (std::vector<std::string>::iterator it = words.begin();
it != words.end(); ++it) {
std::cout << it->length() << " " << word_count[*it] << " " <<
*it << std::endl;
}
return 0;
}
这是Python中的程序:
import fileinput
from collections import defaultdict
words = set()
count = {}
for line in fileinput.input():
line_words = line.split()
for word in line_words:
if word not in words:
words.add(word)
count[word] = 1
else:
count[word] += 1
words = list(words)
words.sort(key=len)
for word in words:
print len(word), count[word], word
对于C++程序,使用的编译器是带有 -O3 标志的 g++ 4.9.0。
使用的 Python 版本是 2.7.3
C++计划所需的时间:
time ./main > measure-and-count.txt < ~/Documents/thesaurus/thesaurus.txt
real 0m0.687s
user 0m0.559s
sys 0m0.123s
Python 程序所花费的时间:
time python main.py > measure-and-count.txt < ~/Documents/thesaurus/thesaurus.txt
real 0m0.369s
user 0m0.308s
sys 0m0.029s
Python 程序比 C++ 程序快得多,输入大小越大,速度也相对较快。这是怎么回事?我对 C++ STL 的使用不正确吗?
编辑:正如评论和答案所建议的那样,我将C++程序更改为使用 std::unordered_set
和 std::unordered_map
.
以下行已更改
#include <unordered_set>
#include <unordered_map>
...
std::unordered_set<std::string> unique_words;
std::unordered_map<std::string,int> word_count;
编译命令:
g++-4.9 -std=c++11 -O3 -o main main.cpp
这仅略微提高了性能:
time ./main > measure-and-count.txt < ~/Documents/thesaurus/thesaurus.txt
real 0m0.604s
user 0m0.479s
sys 0m0.122s
编辑2:C++中更快的程序
这是NetVipeC的解决方案,Dieter Lücking的解决方案以及这个问题的最高答案的组合。真正的性能杀手是默认使用无缓冲读取cin
。用std::cin.sync_with_stdio(false);
解决.此解决方案还使用单个容器,利用C++中的有序map
。
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include <map>
struct comparer_strlen {
bool operator()(const std::string& lhs, const std::string& rhs) const {
if (lhs.length() == rhs.length())
return lhs < rhs;
return lhs.length() < rhs.length();
}
};
int main(int argc, char* argv[]) {
std::cin.sync_with_stdio(false);
std::string str;
typedef std::map<std::string, int, comparer_strlen> word_count_t;
/* Extract words from the input file, splitting on whitespace */
/* Extract unique words and count the number of occurances of each word */
word_count_t word_count;
while (std::cin >> str) {
word_count[str]++;
}
// Print words with length and number of occurances
for (word_count_t::iterator it = word_count.begin();
it != word_count.end(); ++it) {
std::cout << it->first.length() << " " << it->second << " "
<< it->first << 'n';
}
return 0;
}
运行
time ./main3 > measure-and-count.txt < ~/Documents/thesaurus/thesaurus.txt
real 0m0.106s
user 0m0.091s
sys 0m0.012s
Edit3:Daniel提供了一个漂亮而简洁的Python程序版本,它的运行时间与上面的版本大致相同:
import fileinput
from collections import Counter
count = Counter(w for line in fileinput.input() for w in line.split())
for word in sorted(count, key=len):
print len(word), count[word], word
运行:
time python main2.py > measure-and-count.txt.py < ~/Documents/thesaurus/thesaurus.txt
real 0m0.342s
user 0m0.312s
sys 0m0.027s
用这个测试,它必须比原始C++快。
更改包括:
- 消除了向量
words
以保存单词(word_count中已经保存了(。 - 消除了集合
unique_words
(word_count中只有唯一的单词(。 - 删除了单词的第二个副本,不需要。 删除了单词
的排序(地图中的顺序已更新,现在地图中的单词按长度排序,长度相同的单词按字典顺序排列。
#include <vector> #include <string> #include <iostream> #include <set> #include <map> struct comparer_strlen_functor { operator()(const std::string& lhs, const std::string& rhs) const { if (lhs.length() == rhs.length()) return lhs < rhs; return lhs.length() < rhs.length(); } }; int main(int argc, char* argv[]) { std::cin.sync_with_stdio(false); std::string str; typedef std::map<std::string, int, comparer_strlen_functor> word_count_t; /* Extract words from the input file, splitting on whitespace */ /* Extract unique words and count the number of occurances of each word */ word_count_t word_count; while (std::cin >> str) { word_count[str]++; } // Print words with length and number of occurances for (word_count_t::iterator it = word_count.begin(); it != word_count.end(); ++it) { std::cout << it->first.length() << " " << it->second << " " << it->first << "n"; } return 0; }
新版本的读取循环,可以逐行读取和拆分。需要#include <boost/algorithm/string/split.hpp>
while (std::getline(std::cin, str)) {
for (string_split_iterator It = boost::make_split_iterator(
str, boost::first_finder(" ", boost::is_iequal()));
It != string_split_iterator(); ++It) {
if (It->end() - It->begin() != 0)
word_count[boost::copy_range<std::string>(*It)]++;
}
}
在Core i5,8GB RAM,GCC 4.9.0,32位,运行时间为238ms。 按照建议使用std::cin.sync_with_stdio(false);
和n
更新了代码。
进行三项更改,省略额外的向量(在 python 中没有(,为词向量保留内存并避免输出中的 endl (!(:
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include <map>
bool compare_strlen (const std::string &lhs, const std::string &rhs) {
return (lhs.length() < rhs.length());
}
int main (int argc, char *argv[]) {
/* Extract words from the input file, splitting on whitespace */
/* Also count the number of occurances of each word */
std::map<std::string, int> word_count;
{
std::string str;
while (std::cin >> str) {
++word_count[str];
}
}
std::vector<std::string> words;
words.reserve(word_count.size());
for(std::map<std::string, int>::const_iterator w = word_count.begin();
w != word_count.end();
++w)
{
words.push_back(w->first);
}
// Sort by word length
std::sort(words.begin(), words.end(), compare_strlen);
// Print words with length and number of occurances
for (std::vector<std::string>::iterator it = words.begin();
it != words.end();
++it)
{
std::cout << it->length() << " " << word_count[*it] << " " <<
*it << 'n';
}
return 0;
}
给:
源语言:
real 0m0.230s
user 0m0.224s
sys 0m0.004s
改进:
real 0m0.140s
user 0m0.132s
sys 0m0.004s
通过添加std::cin.sync_with_stdio(false);
查看俄勒冈小径的问题,进行了更多改进(:
real 0m0.107s
user 0m0.100s
sys 0m0.004s
NetVipeC 的解决方案带有 std::cin.sync_with_stdio(false);
并将 std::endl 替换为 '':
real 0m0.077s
user 0m0.072s
sys 0m0.004s
蟒:
real 0m0.146s
user 0m0.136s
sys 0m0.008s
std::vector<std::string> words;
/* Extract words from the input file, splitting on whitespace */
while (std::cin >> str) {
words.push_back(str);
}
这需要随着向量的增长不断重复分配/复制/释放操作。要么预先分配向量,要么使用类似列表的东西。
您的C++代码存在一些问题。
首先,您使用的是可变字符串。 这意味着您正在复制它们。 (Python 字符串是不可变的(。 对此进行测试,我发现效果实际上会使C++代码变慢,所以让我们放弃这个。
其次,unordered_
容器可能是个好主意。 测试这一点,我通过交换它们来获得 1/3 的加速(使用boost::hash
算法进行哈希处理(。
第三,您使用std::endl
刷新每行std::cout
。 这似乎很愚蠢。
第四,std::cin.sync_with_stdio(false);
减少std::cin
开销,或者不使用它们。
第五,直接从io构建set
和map
,不要无谓地往返于std::vector
。
这是一个测试程序(硬编码数据约为大小的1/4(,具有不可变字符串(std::shared_ptr<const std::string>
(和具有手动哈希设置的unordered_
容器,以及一些C++11功能,使代码更短一些。
去除大R"(
字符串文字,并将stringstream
替换为 std::cin
。
为了获得更高的性能,请不要使用重量级的流式处理对象。 他们做了很多非常非常偏执的工作。
这是另一个C++版本,我相信它与 python 逐行更匹配。 它试图保留与python版本中相同类型的容器和操作,并进行明显C++特定的调整。 请注意,我从其他答案中提升了sync_with_stdio
优化。
#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <list>
#include <sstream>
#include <iterator>
bool compare_strlen(const std::string &lhs, const std::string &rhs)
{
return lhs.length() < rhs.length();
}
int main (int argc, char *argv[]) {
std::unordered_set<std::string> words;
std::unordered_map<std::string, std::size_t> count;
// Make std::cin use its own buffer to improve I/O performance.
std::cin.sync_with_stdio(false);
// Extract words from the input file line-by-line, splitting on
// whitespace
char line[128] = {}; // Yes, std::vector or std::array would work, too.
while (std::cin.getline(line, sizeof(line) / sizeof(line[0]))) {
// Tokenize
std::istringstream line_stream(line);
std::istream_iterator<std::string> const end;
for(std::istream_iterator<std::string> i(line_stream);
i != end;
++i) {
words.insert(*i);
count[*i]++;
}
}
std::list<std::string> words_list(words.begin(), words.end());
words_list.sort(compare_strlen);
// Print words with length and number of occurences
for (auto const & word : words_list)
std::cout << word.length()
<< ' ' << count[word]
<< ' ' << word
<< 'n';
return 0;
}
结果与您的原始 python 代码和@NetVipeC C++相当。
C++
real 0m0.979s
user 0m0.080s
sys 0m0.016s
蟒
real 0m0.993s
user 0m0.112s
sys 0m0.060s
我有点惊讶这个版本的C++的表现与其他简化的C++答案相当,因为我认为像基于stringstream
的标记化这样的事情肯定会成为一个瓶颈。
std::set
和std::map
都针对查找而不是插入进行了优化。每次更改内容时,它们都必须进行排序/树平衡。您可以尝试使用基于哈希的std::unordered_set
和std::unordered_map
,并且对于您的用例来说会更快。
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 二叉搜索如何比线性搜索更快?
- push_back并插入 C++ STL 中哪个更快?
- 如何使插入排序更快?
- C++,为什么数组比矢量更快,使用更少的内存
- 哪个更快:在 1d 向量中按字符串搜索还是在 2d 向量中按向量搜索?
- 哪种方式更快?究竟发生了什么,我们没有看到什么?
- 使用 int32_t 而不是双精度运行矢量点积是否更快?
- 为什么一种算法在相同的时间复杂度下比另一种算法更快?
- C++模运算符与移位运算符,哪个更快,为什么?
- 遍历向量与数组哪个更快?
- 为什么按值传递QStringView比引用常量更快?
- 更快的C++算术运算
- 为什么C++可执行文件在与较新的libstdc++.so链接时运行得更快?
- 当我不关心顺序并且没有重复项时,更快的擦除删除成语?
- 如果要求比较器是严格的总排序,而不仅仅是严格的弱排序,C++标准算法会更快吗?
- SDL GPU 为什么将两个图像分成两个单独的循环更快?
- 有没有更快的方法可以在 std::vector 中插入元素
- 为什么酷睿i5-6600在非方阵乘法方面比酷睿i9-9960X更快?
- 有没有办法使这段代码更快?