C ++元组与结构

使用std::tuple和data-only struct是否有区别?

 typedef std::tuple<int, double, bool> foo_t; struct bar_t { int id; double value; bool dirty; } 

从我在网上find的,我发现有两个主要的区别: struct更具可读性,而tuple有许多可以使用的通用函数。 应该有什么显着的性能差异? 另外,数据布局是否相互兼容(可互换)?

如果你在你的代码中使用了几个不同的元组,你可以通过凝聚你正在使用的函子的数量来逃脱。 我这样说是因为我经常使用以下forms的函子:

 template<int N> struct tuple_less{ template<typename Tuple> bool operator()(const Tuple& aLeft, const Tuple& aRight) const{ typedef typename boost::tuples::element<N, Tuple>::type value_type; BOOST_CONCEPT_REQUIRES((boost::LessThanComparable<value_type>)); return boost::tuples::get<N>(aLeft) < boost::tuples::get<N>(aRight); } }; 

这可能看起来像是矫枉过正,但是对于struct中的每个地方,我都必须使用一个结构来创build一个新的函数对象,但是对于一个元组,我只需要更改N 更好的是,我可以为每一个元组做这个,而不是为每个结构和每个成员variables创build一个全新的函子。 如果我有N个结构M成员variables,我需要创buildNxM函子(更坏的情况),可以压缩到一点点的代码。

当然,如果你打算使用Tuple的方式,你还需要创buildEnums来处理它们:

 typedef boost::tuples::tuple<double,double,double> JackPot; enum JackPotIndex{ MAX_POT, CURRENT_POT, MIN_POT }; 

和繁荣,你的代码是完全可读的:

 double guessWhatThisIs = boost::tuples::get<CURRENT_POT>(someJackPotTuple); 

因为当你想获得包含在其中的项目时它会自我描述。

元组默认build立了(对于==和!=它比较每个元素,对于<。<= …比较第一,如果相同比较第二…)比较: http : //en.cppreference.com/w/ CPP /效用/元组/ operator_cmp

我们对于元组和结构有类似的讨论,在我的大学的帮助下,我写了一些简单的基准来确定元组和结构之间的性能差异。 我们首先从一个默认的结构和一个元组开始。

 struct StructData { int X; int Y; double Cost; std::string Label; bool operator==(const StructData &rhs) { return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label); } bool operator<(const StructData &rhs) { return X < rhs.X || (X == rhs.X && (Y < rhs.Y || (Y == rhs.Y && (Cost < rhs.Cost || (Cost == rhs.Cost && Label < rhs.Label))))); } }; using TupleData = std::tuple<int, int, double, std::string>; 

然后我们使用Celero来比较我们简单的结构和元组的性能。 以下是使用gcc-4.9.2和clang-4.0.0收集的基准代码和性能结果:

 std::vector<StructData> test_struct_data(const size_t N) { std::vector<StructData> data(N); std::transform(data.begin(), data.end(), data.begin(), [N](auto item) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, N); item.X = dis(gen); item.Y = dis(gen); item.Cost = item.X * item.Y; item.Label = std::to_string(item.Cost); return item; }); return data; } std::vector<TupleData> test_tuple_data(const std::vector<StructData> &input) { std::vector<TupleData> data(input.size()); std::transform(input.cbegin(), input.cend(), data.begin(), [](auto item) { return std::tie(item.X, item.Y, item.Cost, item.Label); }); return data; } constexpr int NumberOfSamples = 10; constexpr int NumberOfIterations = 5; constexpr size_t N = 1000000; auto const sdata = test_struct_data(N); auto const tdata = test_tuple_data(sdata); CELERO_MAIN BASELINE(Sort, struct, NumberOfSamples, NumberOfIterations) { std::vector<StructData> data(sdata.begin(), sdata.end()); std::sort(data.begin(), data.end()); // print(data); } BENCHMARK(Sort, tuple, NumberOfSamples, NumberOfIterations) { std::vector<TupleData> data(tdata.begin(), tdata.end()); std::sort(data.begin(), data.end()); // print(data); } 

使用clang-4.0.0收集性能结果

 Celero Timer resolution: 0.001000 us ----------------------------------------------------------------------------------------------------------------------------------------------- Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec | ----------------------------------------------------------------------------------------------------------------------------------------------- Sort | struct | Null | 10 | 5 | 1.00000 | 196663.40000 | 5.08 | Sort | tuple | Null | 10 | 5 | 0.92471 | 181857.20000 | 5.50 | Complete. 

并使用gcc-4.9.2收集性能结果

 Celero Timer resolution: 0.001000 us ----------------------------------------------------------------------------------------------------------------------------------------------- Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec | ----------------------------------------------------------------------------------------------------------------------------------------------- Sort | struct | Null | 10 | 5 | 1.00000 | 219096.00000 | 4.56 | Sort | tuple | Null | 10 | 5 | 0.91463 | 200391.80000 | 4.99 | Complete. 

从以上的结果我们可以清楚地看到

  • 元组比默认的结构更快

  • clang的二进制产品比gcc的性能更高。 铿锵对海湾合作委员会不是这个讨论的目的,所以我不会深入细节。

我们都知道,为每一个结构体定义写一个==或<或>操作符将是一个痛苦和错误的任务。 让我们用std :: tiereplace我们的自定义比较器,然后重新运行我们的基准。

 bool operator<(const StructData &rhs) { return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label); } Celero Timer resolution: 0.001000 us ----------------------------------------------------------------------------------------------------------------------------------------------- Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec | ----------------------------------------------------------------------------------------------------------------------------------------------- Sort | struct | Null | 10 | 5 | 1.00000 | 200508.20000 | 4.99 | Sort | tuple | Null | 10 | 5 | 0.90033 | 180523.80000 | 5.54 | Complete. 

现在我们可以看到,使用std :: tie使得我们的代码更加优雅,错误更难,但是我们将失去大约1%的性能。 现在我将继续使用std :: tie解决scheme,因为我还收到了一个关于比较浮点数与自定义比较器的警告。

到现在为止,我们还没有任何解决scheme来使我们的结构代码运行得更快。 让我们来看看交换函数,并重写它,看看我们是否可以获得任何性能:

 struct StructData { int X; int Y; double Cost; std::string Label; bool operator==(const StructData &rhs) { return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label); } void swap(StructData & other) { std::swap(X, other.X); std::swap(Y, other.Y); std::swap(Cost, other.Cost); std::swap(Label, other.Label); } bool operator<(const StructData &rhs) { return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label); } }; 

使用clang-4.0.0收集性能结果

 Celero Timer resolution: 0.001000 us ----------------------------------------------------------------------------------------------------------------------------------------------- Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec | ----------------------------------------------------------------------------------------------------------------------------------------------- Sort | struct | Null | 10 | 5 | 1.00000 | 176308.80000 | 5.67 | Sort | tuple | Null | 10 | 5 | 1.02699 | 181067.60000 | 5.52 | Complete. 

并使用gcc-4.9.2收集性能结果

 Celero Timer resolution: 0.001000 us ----------------------------------------------------------------------------------------------------------------------------------------------- Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec | ----------------------------------------------------------------------------------------------------------------------------------------------- Sort | struct | Null | 10 | 5 | 1.00000 | 198844.80000 | 5.03 | Sort | tuple | Null | 10 | 5 | 1.00601 | 200039.80000 | 5.00 | Complete. 

现在我们的结构比现在的元组要快一些(clang大约3%,gcc小于1%),但是,我们需要为我们所有的结构编写自定义的交换函数。

那么,POD结构通常可以(ab)用于低级连续的块读取和序列化。 正如你所说,元组可能在某些情况下更加优化,并支持更多的function。

使用任何更适合的情况,并不是没有一般的偏好。 我认为(但我没有把它作为基准),performance差异不会很大。 数据布局很可能不兼容,也不适用于特定的实现。

就“generics函数”而言,Boost.Fusion值得一些爱…特别是BOOST_FUSION_ADAPT_STRUCT 。

从页面翻阅ABRACADBRA

 namespace demo { struct employee { std::string name; int age; }; } // demo::employee is now a Fusion sequence BOOST_FUSION_ADAPT_STRUCT( demo::employee (std::string, name) (int, age)) 

这意味着所有的Fusionalgorithm现在都适用于struct demo::employee


编辑 :关于性能差异或布局的兼容性, tuple的布局是实现定义如此不兼容(因此你不应该之间任何表示之间施放),一般而言,我希望没有差别性能(至less在发布)感谢get<N>的内联。

不应该有性能差异(即使是微不足道的)。 至less在正常情况下,它们将导致相同的内存布局。 尽pipe如此,他们之间的铸造可能并不需要工作(尽pipe我猜测他们通常会有相当的机会)。

那么,这是一个基准,它不会在struct operator ==()中构造一堆元组。 结果表明,使用元组会产生相当大的性能影响,正如人们所期望的那样,使用POD对性能没有任何影响。 (地址parsing器在逻辑单元甚至看到它之前在指令stream水线中find该值)。

VS2015CE使用默认的“Release”设置在我的机器上运行这个结果的常见结果:

 Structs took 0.0814905 seconds. Tuples took 0.282463 seconds. 

请把它猴子,直到你满意。

 #include <iostream> #include <string> #include <tuple> #include <vector> #include <random> #include <chrono> #include <algorithm> class Timer { public: Timer() { reset(); } void reset() { start = now(); } double getElapsedSeconds() { std::chrono::duration<double> seconds = now() - start; return seconds.count(); } private: static std::chrono::time_point<std::chrono::high_resolution_clock> now() { return std::chrono::high_resolution_clock::now(); } std::chrono::time_point<std::chrono::high_resolution_clock> start; }; struct ST { int X; int Y; double Cost; std::string Label; bool operator==(const ST &rhs) { return (X == rhs.X) && (Y == rhs.Y) && (Cost == rhs.Cost) && (Label == rhs.Label); } bool operator<(const ST &rhs) { if(X > rhs.X) { return false; } if(Y > rhs.Y) { return false; } if(Cost > rhs.Cost) { return false; } if(Label >= rhs.Label) { return false; } return true; } }; using TP = std::tuple<int, int, double, std::string>; std::pair<std::vector<ST>, std::vector<TP>> generate() { std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution<int> dist; constexpr size_t SZ = 1000000; std::pair<std::vector<ST>, std::vector<TP>> p; auto& s = p.first; auto& d = p.second; s.reserve(SZ); d.reserve(SZ); for(size_t i = 0; i < SZ; i++) { s.emplace_back(); auto& sb = s.back(); sb.X = dist(mt); sb.Y = dist(mt); sb.Cost = sb.X * sb.Y; sb.Label = std::to_string(sb.Cost); d.emplace_back(std::tie(sb.X, sb.Y, sb.Cost, sb.Label)); } return p; } int main() { Timer timer; auto p = generate(); auto& structs = p.first; auto& tuples = p.second; timer.reset(); std::sort(structs.begin(), structs.end()); double stSecs = timer.getElapsedSeconds(); timer.reset(); std::sort(tuples.begin(), tuples.end()); double tpSecs = timer.getElapsedSeconds(); std::cout << "Structs took " << stSecs << " seconds.\nTuples took " << tpSecs << " seconds.\n"; std::cin.get(); }