C ++ Tuple vs Struct

Is there a difference between using std::tuple and only for data struct ?

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

From what I found on the Internet, I found that there are two main differences: struct more readable, and tuple is a lot of common functions that you can use. Should there be significant differences in performance? Also, is the data format compatible with each other (interchangeable cast)?

+82
c ++ struct tuples
May 01 '11 at 23:46
source share
7 answers

If you use several different tuples in your code, you can get away with reducing the number of functors you use. I say this because I often used the following forms of functors:

 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); } }; 

This may seem redundant, but for every place inside the structure I would have to create a whole new functor object using the structure, but for the tuple, I just change N Better than that, I can do this for each individual tuple, and not to create a whole new functor for each structure and for each member variable. If I have N structures with M member variables, then for NxM functors I will need to create (the worst case scenario), which can be reduced to one small piece of code.

Naturally, if you are going to follow the Tuple path, you will also need to create Enums to work with them:

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

and arrow, you are fully understandable code:

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

because it describes itself when you want to get the elements contained in it.

+22
May 2 '11 at
source share

We have a similar discussion about the tuple and structure, and I am writing some simple tests using one of my colleges to determine the differences in lead times between the tuple and structure. First, we start with the default structure and the tuple.

 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>; 

Then we use Celero to compare the performance of our simple structure and tuple. Below is the comparative code and the results obtained with gcc-4.9.2 and 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); } 

Performance results compiled with 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. 

And the results obtained with 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. 

From the above results it is clearly seen that

  • Tuple is faster than the default structure

  • Clang binaries have better performance than gcc. clang-vs-gcc is not the purpose of this discussion, so I will not go into details.

We all know that writing == or <or> for each structure definition will be a painful and erroneous task. Let us replace our custom comparator using std :: tie and repeat our test.

 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. 

Now we see that using std :: tie makes our code more elegant and harder to make mistakes, however we will lose about 1% of performance. I will stay with the std :: tie solution, since I also get a warning about comparing floating point numbers with a configured comparator.

So far, we have no solution to make our structure code even faster. Let’s take a look at the swap function and rewrite it to see if we can get any performance:

 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); } }; 

Results obtained using 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. 

And the results obtained with 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. 

Now our structure is slightly faster than the tuple (about 3% with clang and less than 1% with gcc), however we need to write our custom swap function for all our structures.

+19
Oct 28 '16 at 15:59
source share

Tuple built by default (for == and! = It compares each element, for <. <= ... first compares, if it compares the second ...) comparators: http://en.cppreference.com/w/cpp / utility / tuple / operator_cmp

+15
Feb 09 '12 at 18:49
source share

Well, here is a test that does not create a bunch of tuples inside the struct == () operator. It turns out there is a fairly significant performance impact from using a tuple, as you would expect, given that using POD does not affect performance at all. (The address resolver finds the value in the instruction pipeline before the logic unit ever even sees it.)

The general results of running this on my computer using VS2015CE using the "Release" default settings:

 Structs took 0.0814905 seconds. Tuples took 0.282463 seconds. 

Please drive with it until you are satisfied.

 #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(); } 
+4
Dec 21 '16 at 8:27
source share

Well, the POD structure can often be (ab) used when reading and serializing at the lower level of a continuous fragment. As you said, a tuple can be more optimized in certain situations and support more functions.

Use what is more suitable for the situation, there are no general preferences. I think (but I did not compare it) that the differences in performance will not be significant. The data layout is most likely incompatible and implementation specific.

+3
May 01 '11 at 23:53
source share

Regarding the "common function", Boost.Fusion deserves some love ... and especially BOOST_FUSION_ADAPT_STRUCT .

Copy from page: 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)) 

This means that all Fusion algorithms are now applicable to struct demo::employee .




EDIT: Regarding the difference in performance or layout compatibility, the tuple layout is an implementation, therefore it is incompatible (and therefore you should not throw between any views), and in general I would not expect a difference in performance (at least least in Release) thanks to the insert get<N> .

+3
May 02 '11 at 7:03 a.m.
source share

There should be no difference in performance (even slight). At least in the normal case, they will lead to the same memory layout. However, casting between the two is probably not required to work (although I assume there will be a fairly fair chance, as usual).

+1
May 01 '11 at 23:54
source share



All Articles