C++ Tuple vs Struct

Ale*_*oay 84 c++ struct tuples

使用a std::tuple和仅数据之间有什么区别struct吗?

typedef std::tuple<int, double, bool> foo_t;

struct bar_t {
    int id;
    double value;
    bool dirty;
}
Run Code Online (Sandbox Code Playgroud)

从我在网上找到的,我发现有两个主要的区别:struct更具可读性,而tuple有许多可以使用的通用功能.应该有任何显着的性能差异吗?此外,数据布局是否相互兼容(可互换转换)?

whe*_*ies 23

如果您在代码中使用了几个不同的元组,那么您可以通过缩减所使用的仿函数来实现.我这样说是因为我经常使用以下形式的仿函数:

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);
    }
};
Run Code Online (Sandbox Code Playgroud)

这可能看起来有点矫枉过正,但对于结构中的每个位置,我必须使用结构创建一个全新的仿函数对象,但对于元组,我只是改变N.更好的是,我可以为每个元组执行此操作,而不是为每个结构和每个成员变量创建一个全新的仿函数.如果我有N个结构,其中包含M个成员变量,我需要创建NxM仿函数(更糟糕的情况),可以将其压缩到一点点代码.

当然,如果您要使用Tuple方式,那么您还需要创建Enums以便与它们一起工作:

typedef boost::tuples::tuple<double,double,double> JackPot;
enum JackPotIndex{
    MAX_POT,
    CURRENT_POT,
    MIN_POT
};
Run Code Online (Sandbox Code Playgroud)

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

double guessWhatThisIs = boost::tuples::get<CURRENT_POT>(someJackPotTuple);
Run Code Online (Sandbox Code Playgroud)

因为当你想要获取其中包含的项目时它会自我描述.

  • 呃...... C++有函数指针,所以`template <typename C,typename T,TC ::*> struct struct_less {template <typename C> bool operator()(C const&,C const&)const; ``应该是可能的.拼写出来稍微不方便,但它只写了一次. (5认同)

hun*_*tit 19

我们有关于元组和结构的类似讨论,我在一位同事的帮助下编写了一些简单的基准,以确定元组和结构之间的性能差异.我们首先从默认结构和元组开始.

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>;
Run Code Online (Sandbox Code Playgroud)

然后我们使用Celero来比较我们简单的struct和tuple的性能.以下是使用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);
}
Run Code Online (Sandbox Code Playgroud)

使用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.
Run Code Online (Sandbox Code Playgroud)

并使用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.
Run Code Online (Sandbox Code Playgroud)

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

  • 元组比默认结构更快

  • clang的二元产品比gcc具有更高的性能.clang-vs-gcc不是讨论的目的所以我不会深入细节.

我们都知道为每个结构定义编写一个==或<或>运算符将是一个痛苦和错误的任务.让我们使用std :: tie替换我们的自定义比较器并重新运行我们的基准测试.

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.
Run Code Online (Sandbox Code Playgroud)

现在我们可以看到使用std :: tie使我们的代码更加优雅并且更难犯错,但是,我们将失去大约1%的性能.我现在将继续使用std :: tie解决方案,因为我还收到有关将浮点数与自定义比较器进行比较的警告.

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

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);
    }
};
Run Code Online (Sandbox Code Playgroud)

使用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.
Run Code Online (Sandbox Code Playgroud)

并使用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.
Run Code Online (Sandbox Code Playgroud)

现在我们的结构比现在的元组略快(使用clang约为3%,使用gcc约为1%),但是,我们需要为所有结构编写自定义交换函数.


NoS*_*tAl 16

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


Kha*_*arr 7

好吧,这是一个基准测试,它不会在结构体 operator==() 内部构造一堆元组。事实证明,使用 tuple 会对性能产生相当大的影响,正如人们所预期的那样,因为使用 POD 根本没有性能影响。(地址解析器在逻辑单元看到它之前找到指令流水线中的值。)

在我的机器上使用 VS2015CE 使用默认的“发布”设置运行它的常见结果:

Structs took 0.0814905 seconds.
Tuples took 0.282463 seconds.
Run Code Online (Sandbox Code Playgroud)

请随意使用它,直到您满意为止。

#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();
}
Run Code Online (Sandbox Code Playgroud)


Use*_*ess 7

另外,数据布局是否相互兼容(可互换铸造)?

奇怪的是,我看不到对这部分问题的直接答复。

答案是。或者至少不可靠,因为元组的布局未指定。

首先,您的结构是标准布局类型。成员的排序、填充和对齐方式由标准和平台 ABI 的组合明确定义。

如果元组是标准布局类型,并且我们知道字段是按照指定类型的顺序布局的,那么我们可能有信心它会与结构匹配。

元组通常使用继承来实现,采用以下两种方式之一:旧的 Loki/现代 C++ 设计递归样式或较新的可变参数样式。两者都不是标准布局类型,因为两者都违反以下条件:

  1. (C++14 之前)

    • 没有带有非静态数据成员的基类,或者

    • 在最底层的派生类中没有非静态数据成员,并且最多有一个具有非静态数据成员的基类

  2. (适用于 C++14 及更高版本)

    • 具有在同一类中声明的所有非静态数据成员和位字段(全部在派生类中或全部在某个基类中)

因为每个叶基类都包含一个元组元素(注意。单元素元组可能一种标准布局类型,尽管不是非常有用)。因此,我们知道该标准不保证元组具有与结构相同的填充或对齐方式。

此外,值得注意的是,旧的递归式元组通常会以相反的顺序排列数据成员。

有趣的是,它有时在过去的某些编译器和字段类型组合的实践中起作用(在一种情况下,在反转字段顺序后使用递归元组)。它现在肯定不能可靠地工作(跨编译器、版本等),并且从一开始就没有得到保证。


Joh*_*ane 6

从其他答案来看,性能考虑最多是最小的。

所以它确实应该归结为实用性、可读性和可维护性。通常struct更好,因为它创建的类型更容易阅读和理解。

有时,可能需要 a std::tuple(甚至std::pair) 才能以高度通用的方式处理代码。例如,如果没有像std::tuple. std::tie是一个很好的例子,说明何时std::tuple可以改进代码(C++20 之前)。

但是任何可以使用 a 的地方struct,您可能都应该使用 a struct。它将赋予您类型的元素语义含义。这对于理解和使用该类型非常宝贵。反过来,这可以帮助避免愚蠢的错误:

// hard to get wrong; easy to understand
cat.arms = 0;
cat.legs = 4;

// easy to get wrong; hard to understand
std::get<0>(cat) = 0;
std::get<1>(cat) = 4;
Run Code Online (Sandbox Code Playgroud)


orl*_*rlp 4

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

使用更适合情况的东西,没有普遍的偏好。我认为(但我没有对其进行基准测试)性能差异不会很大。数据布局很可能不兼容并且特定于实现。