通信フレームの解析などで意地でもreinterpret_castを使いたくない場合、unionで頑張ってみたらどうなるかの実験。
##コード
測定に使うタイマークラス
Timer.hpp
#pragma once
#include <chrono>
#include <windows.h>
using namespace std;
using namespace std::chrono;
namespace TimerType
{
class CrossPlatform
{
public:
void start()
{
start_ = system_clock::now();
}
double elapsed() const
{
duration<double> d = system_clock::now() - start_;
return d.count();
}
private:
system_clock::time_point start_;
};
class HighAccuracy
{
public:
void start()
{
if (!QueryPerformanceFrequency(&freq_)) throw;
if (!QueryPerformanceCounter(&start_)) throw;
}
double elapsed() const
{
LARGE_INTEGER end;
if (!QueryPerformanceCounter(&end)) throw;
return static_cast<double>(end.QuadPart - start_.QuadPart) / freq_.QuadPart;
}
private:
LARGE_INTEGER freq_;
LARGE_INTEGER start_;
};
}
template<typename T>
class Timer
{
public:
Timer() :timer_()
{
timer_.start();
}
void start()
{
timer_.start();
}
double elapsed() const
{
return timer_.elapsed();
}
private:
T timer_;
};
測定対象
Buffer.hpp
#pragma once
#include <string>
#include <iostream>
#include <cstdint>
#include <iosfwd>
struct Result
{
Result()
:requestStatus()
, optionCode()
, count()
{}
friend ostream& operator<<(ostream& os, const Result& r);
uint16_t requestStatus;
uint16_t optionCode;
uint16_t count;
};
ostream& operator<<(ostream& os, const Result& r)
{
string res;
uint16_t t = r.requestStatus;
res += static_cast<char>(t);
res += static_cast<char>(*(reinterpret_cast<char*>(&t) + 1));
t = r.optionCode;
res += static_cast<char>(t);
res += static_cast<char>(*(reinterpret_cast<char*>(&t) + 1));
t = r.count;
res += static_cast<char>(t);
res += static_cast<char>(*(reinterpret_cast<char*>(&t) + 1));
os << res;
return os;
}
#pragma pack(1)
struct Header
{
static const int headerPaddingByte_ = 4;
uint16_t messageByte;
uint16_t optionByte;
size_t getMessageOffset()const
{
return sizeof(Header)+headerPaddingByte_;
}
size_t getOptionOffset()const
{
return sizeof(Header)+headerPaddingByte_ + messageByte;
}
size_t getFooterOffset()const
{
return sizeof(Header)+headerPaddingByte_ + messageByte + optionByte;
}
};
struct Message
{
uint16_t requestStatus;
uint8_t info1;
uint8_t info2;
uint8_t info3;
uint8_t info4;
};
struct Option
{
uint16_t optionCode;
uint8_t info1;
uint8_t info2;
uint8_t info3;
uint8_t info4;
};
struct Footer
{
uint16_t count;
uint8_t info1;
uint8_t info2;
uint8_t info3;
uint8_t info4;
};
template<typename T>
union BufferAccessor
{
public:
BufferAccessor(uint8_t* b)
{
head = b;
}
uint8_t* head;
T* data;
};
class HeaderAccessor
{
public:
HeaderAccessor(uint8_t* b) :h_(b){}
uint8_t* getMessage()
{
return h_.head + h_.data->getMessageOffset();
}
uint8_t* getOption()
{
return h_.head + h_.data->getOptionOffset();
}
uint8_t* getFooter()
{
return h_.head + h_.data->getFooterOffset();
}
private:
BufferAccessor<Header> h_;
};
#pragma pack()
//Unionで頑張る
class BufferAnalyzeUnion
{
public:
void operator()(uint8_t* b, Result & r)
{
HeaderAccessor h(b);
BufferAccessor<Message> m(h.getMessage());
r.requestStatus = m.data->requestStatus;
BufferAccessor<Option> o(h.getOption());
r.optionCode = o.data->optionCode;
BufferAccessor<Footer> f(h.getFooter());
r.count = f.data->count;
}
};
//直接読み出す
class BufferAnalyzeDirect
{
public:
static const size_t msgOfs_ = offsetof(Header, messageByte);
static const size_t optOfs_ = offsetof(Header, optionByte);
void operator()(uint8_t* b, Result & r)
{
r.requestStatus = *reinterpret_cast<uint16_t*>(&b[4 + 4]);
uint16_t msgByte = *reinterpret_cast<uint16_t*>(&b[msgOfs_]);
r.optionCode = *reinterpret_cast<uint16_t*>(&b[4 + 4 + msgByte]);
uint16_t optByte = *reinterpret_cast<uint16_t*>(&b[optOfs_]);
r.count = *reinterpret_cast<uint16_t*>(&b[4 + 4 + msgByte + optByte]);
}
};
//一回ずつフレーム情報を表現する構造体のポインタに変換する方式
class BufferAnalyzeConvert
{
public:
void operator()(uint8_t* b, Result & r)
{
Header* h = reinterpret_cast<Header*>(b);
Message* m = reinterpret_cast<Message*>(&b[sizeof(Header)+Header::headerPaddingByte_]);
r.requestStatus = m->requestStatus;
Option* o = reinterpret_cast<Option*>(&b[sizeof(Header)+Header::headerPaddingByte_ + h->messageByte]);
r.optionCode = o->optionCode;
Footer* f = reinterpret_cast<Footer*>(&b[sizeof(Header)+Header::headerPaddingByte_ + h->messageByte + h->optionByte]);
r.count = f->count;
}
};
//void
class BufferAnalyzeVoid
{
public:
void operator()(uint8_t* b, Result & r)
{
Header* h = static_cast<Header*>(static_cast<void*>(b));
Message* m = static_cast<Message*>(static_cast<void*>(&b[sizeof(Header)+Header::headerPaddingByte_]));
r.requestStatus = m->requestStatus;
Option* o = static_cast<Option*>(static_cast<void*>(&b[sizeof(Header)+Header::headerPaddingByte_ + h->messageByte]));
r.optionCode = o->optionCode;
Footer* f = static_cast<Footer*>(static_cast<void*>(&b[sizeof(Header)+Header::headerPaddingByte_ + h->messageByte + h->optionByte]));
r.count = f->count;
}
};
//Offsetでデータの位置を突き止めてmemcpyする
class BufferAnalyzeOffsetMemcpy
{
public:
static const size_t messageByteSize_ = sizeof(((Header*)0)->messageByte);
static const size_t optionByteSize_ = sizeof(((Header*)0)->optionByte);
static const size_t requestStatusSize_ = sizeof(((Message*)0)->requestStatus);
static const size_t optionCodeSize_ = sizeof(((Option*)0)->optionCode);
static const size_t countSize_ = sizeof(((Footer*)0)->count);
void operator()(uint8_t* b, Result & r)
{
size_t msgByte = 0;
size_t optByte = 0;
memcpy(&msgByte, &b[offsetof(Header, messageByte)], messageByteSize_);
memcpy(&optByte, &b[offsetof(Header, optionByte)], optionByteSize_);
memcpy(&r.requestStatus,&b[sizeof(Header)+Header::headerPaddingByte_ + offsetof(Message, requestStatus)], requestStatusSize_);
memcpy(&r.optionCode, &b[sizeof(Header)+Header::headerPaddingByte_ + msgByte + offsetof(Option, optionCode) ], optionCodeSize_);
memcpy(&r.count, &b[sizeof(Header)+Header::headerPaddingByte_ + msgByte + optByte + offsetof(Footer, count) ], countSize_);
}
};
//Offsetでデータの位置を突き止めてシフト演算で復元する
class BufferAnalyzeOffsetShift
{
public:
static const size_t messageByteSize_ = sizeof(((Header*)0)->messageByte);
static const size_t optionByteSize_ = sizeof(((Header*)0)->optionByte);
static const size_t requestStatusSize_ = sizeof(((Message*)0)->requestStatus);
static const size_t optionCodeSize_ = sizeof(((Option*)0)->optionCode);
static const size_t countSize_ = sizeof(((Footer*)0)->count);
void operator()(uint8_t* b, Result & r)
{
size_t msgByte = 0;
size_t optByte = 0;
#define get(a) b[a] + (b[a + 1] << 8)
msgByte = get(offsetof(Header, messageByte));
optByte = get(offsetof(Header, optionByte));
r.requestStatus = get(sizeof(Header)+Header::headerPaddingByte_ + offsetof(Message, requestStatus));
r.optionCode = get(sizeof(Header)+Header::headerPaddingByte_ + msgByte + offsetof(Option, optionCode));
r.count = get(sizeof(Header)+Header::headerPaddingByte_ + msgByte + optByte + offsetof(Footer, count));
#undef get
}
};
呼び出し側
main.cpp
#include <iostream>
#include "Timer.hpp"
#include "Buffer.hpp"
template<typename T, typename U>
class Measure
{
static const uint64_t rep = 100000000;
U r_;
public:
Measure() :r_()
{}
U get()
{
return r_;
}
double execute(uint8_t* buffer)
{
Timer<TimerType::HighAccuracy> timer;
for (uint64_t i = 0; i < rep; ++i)
{
T()(buffer, r_);
}
return timer.elapsed();
}
};
void setup(uint8_t* buffer, size_t e)
{
for (size_t i = 0; i < e; ++i)
{
buffer[i] = i;
}
uint16_t* messageByte = reinterpret_cast<uint16_t*>(&buffer[0]);
*messageByte = 32;
uint16_t* optionByte = reinterpret_cast<uint16_t*>(&buffer[2]);
*optionByte = 32;
buffer[4 + 4] = -108;
buffer[4 + 4 + 1] = 103;
buffer[4 + 4 + 32] = -109;
buffer[4 + 4 + 32 + 1] = -82;
buffer[4 + 4 + 32 + 32] = -106;
buffer[4 + 4 + 32 + 32 + 1] = 67;
}
#include <iomanip>
#define measure(n,m,v) \
{ \
Measure<n##m, Result> a; \
volatile double r = a.execute(v); \
auto c = a.get(); \
cout << setiosflags(ios::left) << setw(15) << #m":" << fixed << setprecision(6) << r << ", " << c << endl; \
}
int main(int argv, char* argc[])
{
uint8_t sampleData[1024] = {};
setup(sampleData, sizeof(sampleData) / sizeof(sampleData[0]));
measure(BufferAnalyze, Union, sampleData);
measure(BufferAnalyze, Direct, sampleData);
measure(BufferAnalyze, Convert, sampleData);
measure(BufferAnalyze, Void, sampleData);
measure(BufferAnalyze, OffsetMemcpy, sampleData);
measure(BufferAnalyze, OffsetShift, sampleData);
return 0;
}
##環境
Visual studio 2013 express
Releaseビルド
実行結果
Union: 0.098571, 波動砲
Direct: 0.098712, 波動砲
Convert: 0.098260, 波動砲
Void: 0.097815, 波動砲
OffsetMemcpy: 1.100283, 波動砲
OffsetShift: 0.278266, 波動砲
続行するには何かキーを押してください . . .
##考察
1024Byteのバッファに対して適当な場所に文字列を入れてそれが正しく読み出せていることを確認してみた。
入力値は"波動砲"。
結果は全部"波動砲"が出ているのでOK。
PC環境なので実行するたびにバラツキがでるが、どの方式もさほど性能的には差が出なかった。
reinterpret_castを使わず、性能も落とさずに済む方法があることは分かったが、これだけの手間かけるなら素直にreinterpret_castした方がいいような気もする・・・
(2/15 追記)
直接読み出しバージョンがリテラルでオフセット突っ込むというチート行為を働いていたので修正。
あと、yoh2様からアドバイスいただいたvoid*バージョンとoffsetof使うバージョンも書いてみた。
offsetof使うバージョンは可読性悪い上に遅かった。