最近,我在接受采访时被要求使用线程实现字符串反转功能。我想出了下面解决方案的大部分内容。选择与否是一个不同的故事:-)。我试图在运行Windows 8消费者预览版的家用PC上运行以下解决方案。编译器是VC11 Beta。
问题是,多线程代码总是比顺序代码慢或慢1毫秒。我给出的输入是一个大小为32.4 MB的文本文件。有没有办法让多线程代码更快?或者给出的输入是否太小而无法产生任何差异?
修改
我只写了void Reverse(char* str, int beg, int end, int rbegin, int rend);
和
面试中的void CustomReverse(char* str);
方法。所有其他代码都写在家里。
template<typename Function>
void TimeIt(Function&& fun, const char* caption)
{
clock_t start = clock();
fun();
clock_t ticks = clock()-start;
std::cout << std::setw(30) << caption << ": " << (double)ticks/CLOCKS_PER_SEC << "\n";
}
void Reverse(char* str)
{
assert(str != NULL);
for ( int i = 0, j = strlen(str) - 1; i < j; ++i, --j)
{
if ( str[i] != str[j])
{
std::swap(str[i], str[j]);
}
}
}
void Reverse(char* str, int beg, int end, int rbegin, int rend)
{
for ( ; beg <= end && rbegin >= rend; ++beg, --rbegin)
{
if ( str[beg] != str[rbegin])
{
char temp = str[beg];
str[beg] = str[rbegin];
str[rbegin] = temp;
}
}
}
void CustomReverse(char* str)
{
int len = strlen(str);
const int MAX_THREADS = std::thread::hardware_concurrency();
std::vector<std::thread> threads;
threads.reserve(MAX_THREADS);
const int CHUNK = len / MAX_THREADS > (4096) ? (4096) : len / MAX_THREADS;
/*std::cout << "len:" << len << "\n";
std::cout << "MAX_THREADS:" << MAX_THREADS << "\n";
std::cout << "CHUNK:" << CHUNK << "\n";*/
for ( int i = 0, j = len - 1; i < j; )
{
if (i + CHUNK < j && j - CHUNK > i )
{
for ( int k = 0; k < MAX_THREADS && (i + CHUNK < j && j - CHUNK > i ); ++k)
{
threads.push_back( std::thread([=, &str]() { Reverse(str, i,
i + CHUNK, j, j - CHUNK); }));
i += CHUNK + 1;
j -= CHUNK + 1;
}
for ( auto& th : threads)
{
th.join();
}
threads.clear();
}
else
{
char temp = str[i];
str[i] = str[j];
str[j] = str[i];
i++;
j--;
}
}
}
void Write(std::ostream&& os, const std::string& str)
{
os << str << "\n";
}
void CustomReverseDemo(int argc, char** argv)
{
std::ifstream inpfile;
for ( int i = 0; i < argc; ++i)
std::cout << argv[i] << "\n";
inpfile.open(argv[1], std::ios::in);
std::ostringstream oss;
std::string line;
if (! inpfile.is_open())
{
return;
}
while (std::getline(inpfile, line))
{
oss << line;
}
std::string seq(oss.str());
std::string par(oss.str());
std::cout << "Reversing now\n";
TimeIt( [&] { CustomReverse(&par[0]); }, "Using parallel code\n");
TimeIt( [&] { Reverse(&seq[0]) ;}, "Using Sequential Code\n");
TimeIt( [&] { Reverse(&seq[0]) ;}, "Using Sequential Code\n");
TimeIt( [&] { CustomReverse(&par[0]); }, "Using parallel code\n");
Write(std::ofstream("sequential.txt"), seq);
Write(std::ofstream("Parallel.txt"), par);
}
int main(int argc, char* argv[])
{
CustomReverseDemo(argc, argv);
}
答案 0 :(得分:5)
我发现代码难以理解,但我发现了以下问题:
将字符串静态分区为MAX_THREADS块并启动MAX_THREADS个线程。有更有效的方法可以做到这一点,但至少这会给你一些加速。
答案 1 :(得分:1)
在使用所有新的线程功能时,您没有使用标准库的所有旧部件,例如std::string
和iterators
您不应该自己编写线程内容,而是使用并行算法库,它提供类似parallel_for
构造的内容。
您的任务可以简化为:
std::string str;
// fill string
auto worker = [&] (iter begin, iter end) {
for(auto it = begin; it != end; ++it) {
std::swap(*it, *(std::end(str) - std::distance(std::begin(str), it) - 1));
}
};
parallel_for(std::begin(str),
std::begin(str) + std::distance(std::begin(str), std::end(str)) / 2, worker);
请注意,您需要相当大的文本文件才能加快这种并行方法的速度。 34 MB可能还不够。
在小字符串上,false sharing之类的效果可能会对您的表现产生负面影响。
答案 2 :(得分:1)
小事:
答案 3 :(得分:1)
从300 MB字符串大小开始,我发现多线程版本(基于TBB,见下文)的性能平均比串行版本好3倍。不得不承认,对于这个3倍的加速,它使用12个真正的核心:)。我尝试了一些粒度(你可以在TBB中为blocked_range类对象指定那些),但是这没有产生任何重大影响,默认的auto_partitioner似乎能够几乎最佳地对数据进行分区。我使用的代码:
tbb::parallel_for(tbb::blocked_range<size_t>(0, (int)str.length()/2), [&] (const tbb::blocked_range<size_t>& r) {
const size_t r_end = r.end();
for(size_t i = r.begin(); i < r_end; ++i) {
std::swap(*(std::begin(str) + i), *(std::end(str) - 1 - i));
}
});
答案 4 :(得分:1)
我尝试用相同的功能编写程序: My effort of "Reversing a string using threads"
我已经在Windows 7上测试了带有VC11 Beta和mingw(gcc 4.8)的2核心处理器
测试结果:
VC11 Beta:
7 Mb 档案:
调试
简单反转:0.468
异步反转:0.275
推出
简单反转:0.006
异步反转:0.014
98 Mb 档案:
调试
简单反转:5.982
异步反转:3.091
推出
简单反转:0.063
异步反转:0.079
782 Mb 档案
推出
简单反转:0.567
异步反转:0.689
<强> MINGW:强>
782 Mb 档案
推出
简单反转:0.583
异步反转:0.566
正如您所看到的,多线程代码仅在调试版本中获胜。但是在发布编译器中,即使在单线程代码的情况下也会进行优化并使用所有内核。
所以相信你的编译器=)
答案 5 :(得分:0)
经过测试的代码
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <string.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
void strrev(char *p, char *q, int num)
{
for(int i=0;i < num ; ++i,--q, ++p)
*q = *p;
}
int main(int argc, char **argv)
{
char *str;
if(argc>1)
{
str = argv[1];
printf("String to be reversed %s\n", str);
}
else
{
return 0;
}
int length = strlen(str);
int N = 5;
char *rev_str = (char *)malloc(length+1);
rev_str[length] = '\0';
if (N>length)
{
N = length;
}
std::vector<std::thread> threads;
int begin=0, end=length-1, k = length/N;
for(int i=1; i <= N; ++i)
{
threads.emplace_back(strrev, &str[begin], &rev_str[end], k);
//strrev(&str[begin], &rev_str[end], k);
begin += k;
end -= k;
}
while (true)
{
if (end < 0 && begin > length-1)
{
break;
}
rev_str[end] = str[begin];
--end; ++begin;
}
for (auto& i: threads)
{
i.join();
}
printf("String after reversal %s\n", rev_str);
return 0;
}