C ++ Tuple против Struct - PullRequest
       10

C ++ Tuple против Struct

80 голосов
/ 02 мая 2011

Есть ли какая-либо разница между использованием std::tuple и только для данных struct?

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

struct bar_t {
    int id;
    double value;
    bool dirty;
}

Из того, что я нашел в Интернете, я обнаружил, что есть два основных различия:1006 * более читабелен, в то время как tuple имеет много общих функций, которые можно использовать.Должна ли быть какая-то существенная разница в производительности?Кроме того, совместимо ли расположение данных друг с другом (взаимозаменяемо)?

Ответы [ 8 ]

21 голосов
/ 02 мая 2011

Если вы используете несколько разных кортежей в своем коде, вы можете избежать сокращения количества используемых вами функторов. Я говорю это, потому что я часто использовал следующие формы функторов:

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

Это может показаться излишним, но для каждого места в структуре я должен был бы создать новый объект-функтор, используя структуру, но для кортежа я просто изменил N. Более того, я могу сделать это для каждого кортежа, в отличие от создания целого нового функтора для каждой структуры и для каждой переменной-члена. Если бы у меня было N структур с переменными-членами M, которые функторы NxM, мне нужно было бы создать (в худшем случае), которые можно сжать до одного куска кода.

Естественно, если вы собираетесь пойти по пути Tuple, вам также потребуется создать Enums для работы с ними:

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

и бум, ваш код полностью читабелен:

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

потому что он описывает себя, когда вы хотите получить содержащиеся в нем элементы.

18 голосов
/ 28 октября 2016

У нас аналогичная дискуссия о кортеже и структуре, и я с помощью одного из моих коллег пишу несколько простых тестов для определения различий в производительности между кортежем и структурой. Сначала мы начнем со структуры по умолчанию и кортежа.

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

Теперь мы можем видеть, что использование 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);
    }
};

Результаты производительности, собранные с использованием 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.

Теперь наша структура теперь немного быстрее, чем у кортежа (около 3% для clang и менее 1% для gcc), однако нам нужно написать нашу собственную функцию подкачки для всех наших структур.

14 голосов
/ 09 февраля 2012

Tuple имеет встроенный по умолчанию (для == и! = Он сравнивает каждый элемент, для <. <= ... первое сравнение, если то же сравнивает второй ...) компараторы: <a href="http://en.cppreference.com/w/cpp/utility/tuple/operator_cmp" rel="noreferrer">http://en.cppreference.com/w/cpp/utility/tuple/operator_cmp

4 голосов
/ 21 декабря 2016

Хорошо, вот эталонный тест, который не создает кучу кортежей внутри оператора структуры == ().Оказывается, что использование кортежей имеет довольно значительное влияние на производительность, как и следовало ожидать, учитывая, что использование POD вообще не влияет на производительность.(Преобразователь адресов находит значение в конвейере команд еще до того, как его увидит логическое устройство.)

Общие результаты выполнения этого на моем компьютере с 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();
}
3 голосов
/ 02 мая 2011

Что касается «универсальной функции», 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))

Это означает, что все алгоритмы Fusion теперь применимы к struct demo::employee.


EDIT : что касается разницы в производительности или совместимости компоновки,Макет tuple определяется реализацией, поэтому он не совместим (и, следовательно, вы не должны приводить между этими двумя представлениями), и в целом я не ожидал бы никакой разницы в производительности (по крайней мере, в Release) благодаря встраиванию get<N>.

3 голосов
/ 02 мая 2011

Что ж, структура POD часто (ab) может использоваться при последовательном чтении и сериализации низкоуровневых блоков. Как вы сказали, кортеж может быть более оптимизирован в определенных ситуациях и поддерживать больше функций.

Используйте то, что больше подходит для ситуации, здесь нет общих предпочтений. Я думаю (но я не проверял это), что различия в производительности не будут значительными. Расположение данных, скорее всего, несовместимо и зависит от конкретной реализации.

1 голос
/ 02 мая 2011

Не должно быть разницы в производительности (даже незначительной).По крайней мере, в нормальном случае они приведут к той же схеме памяти.Тем не менее, приведение между ними, вероятно, не требуется для работы (хотя я предполагаю, что вполне вероятно, что это нормально).

0 голосов
/ 24 августа 2018

Я знаю, что это старая тема, но сейчас я собираюсь принять решение относительно части моего проекта: идти ли мне путём или структурой.После прочтения этой темы у меня появилось несколько идей.
1. Что касается wheaties и теста производительности - пожалуйста, обратите внимание, что вы обычно можете использовать memcpy, memset и подобные трюки для структур.Это сделало бы производительность НАМНОГО больше, чем наборы.
2. Я вижу некоторые преимущества в кортежах:
a) вы можете использовать кортежи для возврата набора переменных из функции или метода и уменьшить количество используемых вами типов,
b) основываясь на том факте, что кортеж имеет предопределенные операторы <, ==,>, вы также можете использовать кортеж в качестве ключа на карте или hash_map, что намного более экономически выгодно, чем структура, где вам нужно реализовать эти операторы.
Я начал копать www и в итоге достиг этой страницы:
https://arne -mertz.de / 2017/03 / smelly-pair-tuple /
Как правило, я согласен с окончательным выводом извыше.

...