Skip to content

Instantly share code, notes, and snippets.

@Dugy

Dugy/exception_performance.cpp Secret

Created Nov 22, 2020
Embed
What would you like to do?
//usr/bin/g++ $0 -o ${o=`mktemp`} && exec $o $*
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <memory>
#include <chrono>
#include <charconv>
#include <cstring>
#ifdef USE_ERROR_CODES
enum ErrorType {
SUCCESS = 0,
PARSE_ERROR = 1,
EXPECTED_NUMBER = 2,
INVALID_RESOURCE_ERROR = 3,
INVALID_PACKET = 4,
NOT_ENOUGH_DATA = 5
};
#else
struct ParseError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct InvalidResourceError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct NetworkError : std::runtime_error {
using std::runtime_error::runtime_error;
};
#endif
// Mostly test 1
struct Xml {
std::string name;
std::vector<std::pair<std::string, std::string>> attributes;
std::vector<std::shared_ptr<Xml>> children;
std::string text;
#ifdef USE_ERROR_CODES
ErrorType getAttribute(const std::string& attributeName, std::string& out) const {
for (auto& it : attributes)
if (it.first == attributeName) {
out = it.second;
return SUCCESS;
}
return INVALID_RESOURCE_ERROR;
}
ErrorType getChild(const std::string& childName, std::shared_ptr<Xml>& out) const {
for (auto& it : children)
if (it->name == childName) {
out = it;
return SUCCESS;
}
return INVALID_RESOURCE_ERROR;
}
#else
std::string getAttribute(const std::string& attributeName) const {
for (auto& it : attributes)
if (it.first == attributeName) {
return it.second;
}
#ifdef __cpp_exceptions
throw InvalidResourceError("Missing attribute " + name);
#else
std::cerr << "Missing attribute " << attributeName << std::endl;
exit(4);
#endif
}
std::shared_ptr<Xml> getChild(const std::string& childName) const {
for (auto& it : children)
if (it->name == childName) {
return it;
}
#ifdef __cpp_exceptions
throw InvalidResourceError("Missing child " + name);
#else
std::cerr << "Missing child " << childName << std::endl;
exit(4);
#endif
}
#endif
void write(std::ostream& stream, int depth = 0) const {
auto indent = [depth, &stream] {
for (int i = 0; i < depth; i++)
stream << '\t';
};
indent();
stream << '<' << name;
for (auto& it : attributes) {
stream << ' ' << it.first << "=\"" << it.second << '\"';
}
if (children.empty() && text.empty()) {
stream << "/>\n";
} else {
stream << ">\n";
if (!text.empty()) {
stream << text << '\n';
}
if (!children.empty()) {
for (auto& it : children) {
it->write(stream, depth + 1);
}
}
indent();
stream << "</" << name << ">\n";
}
}
friend std::ostream& operator<<(std::ostream& stream, const Xml& self) {
self.write(stream, 0);
return stream;
}
};
#ifndef __cpp_exceptions
void fail(const std::string& problem) {
// No idea why, but the performance is better if this function is defined but never used
std::cerr << problem << std::endl;
exit(3);
}
#endif
#ifdef USE_ERROR_CODES
ErrorType parseXml(const char*& source, std::shared_ptr<Xml>& result) {
#else
std::shared_ptr<Xml> parseXml(const char*& source) {
#endif
auto absorbWhitespace = [&source] {
while (*source == ' ' || *source == '\n' || *source == '\t' || *source == '\r')
source++;
};
#ifdef USE_ERROR_CODES
#define REPORT_PARSE_ERROR return PARSE_ERROR
#define READ_STRING(TARGET, FORBIDDEN) if (readString(TARGET, FORBIDDEN)) return PARSE_ERROR
#else
#ifdef __cpp_exceptions
#define REPORT_PARSE_ERROR throw ParseError("Failed to parse")
#else
#define REPORT_PARSE_ERROR { std::cerr << "Parse error" << std::endl; exit(3); }
#endif
#define READ_STRING(TARGET, FORBIDDEN) readString(TARGET, FORBIDDEN)
#endif
auto readString = [&source] (std::string& made, const char* forbidden) {
while (true) {
bool stop = false;
for (int i = 0; forbidden[i]; i++)
if (*source == forbidden[i]) {
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return;
#endif
}
if (*source == '\0')
REPORT_PARSE_ERROR;
made.push_back(*source);
source++;
}
};
absorbWhitespace();
if (*source == '\0') {
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return nullptr;
#endif
}
#ifdef USE_ERROR_CODES
result = std::make_shared<Xml>();
#endif
// Tag start
if (*source != '<') {
REPORT_PARSE_ERROR;
}
source++;
while (*source == '!') {
// Comments
for (int i = 0; i < 2; i++) {
source++;
if (*source != '-') {
REPORT_PARSE_ERROR;
}
}
do source++;
while (*source != '>' || source[-1] != '-' || source[-2] != '-');
source++;
absorbWhitespace();
source++;
}
absorbWhitespace();
// It's a tag
#ifndef USE_ERROR_CODES
std::shared_ptr<Xml> result = std::make_shared<Xml>();
#endif
READ_STRING(result->name, "\n\r\t >/");
absorbWhitespace();
// Parse attributes
while (*source != '>' && (*source != '/' || source[1] != '>')) {
std::string attributeName;
READ_STRING(attributeName, "\n\r\t =/>");
if (*source != '=')
REPORT_PARSE_ERROR;
source++;
if (*source != '\"')
REPORT_PARSE_ERROR;
source++;
std::string attribute;
READ_STRING(attribute, "\"");
source++;
absorbWhitespace();
result->attributes.push_back(std::make_pair(attributeName, attribute));
}
if (*source == '/') {
// It's a tag without children
source += 2;
absorbWhitespace();
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return result;
#endif
}
// The tag has children
source++;
absorbWhitespace();
while (*source != '<' || source[1] != '/') {
if (*source != '<') {
result->text.push_back(*source);
source++;
} else {
#ifdef USE_ERROR_CODES
std::shared_ptr<Xml> child;
if (parseXml(source, child))
return PARSE_ERROR;
result->children.push_back(child);
#else
result->children.push_back(parseXml(source));
#endif
}
}
source += 2;
std::string check;
READ_STRING(check, "\n\t\r >");
if (check != result->name)
REPORT_PARSE_ERROR;
source++;
absorbWhitespace();
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return result;
#endif
}
#undef READ_STRING
// Mostly test 2
#ifdef USE_ERROR_CODES
ErrorType parseXmlDocument(const char* str, std::vector<std::shared_ptr<Xml>>& result) {
#else
std::vector<std::shared_ptr<Xml>> parseXmlDocument(const char* str) {
std::vector<std::shared_ptr<Xml>> result;
#endif
// Eat the header
if (*str == '<' && str[1] == '?') {
str += 2;
while (*str != '>' || str[-1] != '?') {
if (*str == '\0')
REPORT_PARSE_ERROR;
str++;
}
str++;
}
std::shared_ptr<Xml> part;
do {
#ifdef USE_ERROR_CODES
part = nullptr;
if (parseXml(str, part))
return PARSE_ERROR;
#else
part = parseXml(str);
#endif
if (part)
result.push_back(part);
} while (part);
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return result;
#endif
}
#undef REPORT_PARSE_ERROR
#ifdef USE_ERROR_CODES
#define PROPAGATE_ERROR(CODE) { auto problem = CODE; if (problem) return problem; }
#endif
unsigned int now() {
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
#ifdef __cpp_exceptions
#define NETWORK_ERROR_OR_ABORT(MESSAGE) throw NetworkError(MESSAGE)
#else
#define NETWORK_ERROR_OR_ABORT(MESSAGE) { std::cerr << MESSAGE << std::endl; exit(6); }
#endif
struct TestClass {
#ifdef USE_ERROR_CODES
static ErrorType parseNumber(const std::string& from, int& to) {
auto result = std::from_chars(from.data(), from.data() + from.size(), to);
if (result.ptr != from.data())
return SUCCESS;
else
return EXPECTED_NUMBER;
}
static ErrorType parseAttribute(const Xml& source, const std::string& name, int& result) {
std::string attribute;
auto problem = source.getAttribute(name, attribute);
if (problem)
return problem;
problem = parseNumber(attribute, result);
return problem;
}
template <typename Child>
static ErrorType setupChild(const Xml& source, const std::string& name, Child& target) {
std::shared_ptr<Xml> got;
PROPAGATE_ERROR(source.getChild(name, got));
PROPAGATE_ERROR(target.fromXml(*got));
return SUCCESS;
}
#else
static int parseNumber(const std::string& from) {
int to;
auto result = std::from_chars(from.data(), from.data() + from.size(), to);
if (result.ptr != from.data())
return to;
else
#ifdef __cpp_exceptions
throw std::runtime_error("Expected number, got " + from);
#else
{
std::cerr << "Expected number, got " << from << std::endl;
exit(4);
}
#endif
return 0;
}
static float parseAttribute(const Xml& source, const std::string& attributeName) {
return parseNumber(source.getAttribute(attributeName));
}
#endif
struct Stats {
int damage;
int life;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(parseAttribute(source, "damage", damage));
PROPAGATE_ERROR(parseAttribute(source, "life", life));
return SUCCESS;
}
#else
Stats(const Xml& source) :
damage(parseAttribute(source, "damage")),
life(parseAttribute(source, "life")) {
}
#endif
};
struct Appearance {
std::string mesh;
std::string icon;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(source.getAttribute("mesh", mesh));
PROPAGATE_ERROR(source.getAttribute("icon", icon));
return SUCCESS;
}
#else
Appearance(const Xml& source) :
mesh(source.getAttribute("mesh")),
icon(source.getAttribute("icon")) {
}
#endif
};
struct Skill {
struct Stats {
int damage;
int speed;
int area;
std::string type;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(parseAttribute(source, "damage", damage));
PROPAGATE_ERROR(parseAttribute(source, "speed", speed));
PROPAGATE_ERROR(parseAttribute(source, "area", area));
PROPAGATE_ERROR(source.getAttribute("type", type));
return SUCCESS;
}
#else
Stats(const Xml& source) :
damage(parseAttribute(source, "damage")),
speed(parseAttribute(source, "speed")),
area(parseAttribute(source, "area")),
type(source.getAttribute("type")) {
}
#endif
};
struct Animation {
int speed;
std::string name;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(parseAttribute(source, "speed", speed));
PROPAGATE_ERROR(source.getAttribute("name", name));
return SUCCESS;
}
#else
Animation(const Xml& source) :
speed(parseAttribute(source, "speed")),
name(source.getAttribute("name")) {
}
#endif
};
struct Effect {
std::string particle;
std::string colour;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(source.getAttribute("particle", particle));
PROPAGATE_ERROR(source.getAttribute("colour", colour));
return SUCCESS;
}
#else
Effect(const Xml& source) :
particle(source.getAttribute("particle")),
colour(source.getAttribute("colour")) {
}
#endif
};
std::string name;
Stats stats;
Animation animation;
Effect effect;
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(source.getAttribute("name", name));
PROPAGATE_ERROR(setupChild(source, "stats", stats));
PROPAGATE_ERROR(setupChild(source, "animation", animation));
PROPAGATE_ERROR(setupChild(source, "effect", effect));
return SUCCESS;
}
#else
Skill(const Xml& source) :
name(source.getAttribute("name")),
stats(*source.getChild("stats")),
animation(*source.getChild("animation")),
effect(*source.getChild("effect")) {
}
#endif
};
std::string id;
std::string description;
Stats stats;
Appearance appearance;
std::vector<Skill> skills;
int x = rand() % 128 - 64;
int y = rand() % 128 - 64;
int life;
unsigned int lastAttack = now();
#ifdef USE_ERROR_CODES
ErrorType fromXml(const Xml& source) {
PROPAGATE_ERROR(source.getAttribute("id", id));
PROPAGATE_ERROR(setupChild(source, "stats", stats));
PROPAGATE_ERROR(setupChild(source, "appearance", appearance));
for (auto& it : source.children)
if (it->name == "skill") {
skills.emplace_back();
PROPAGATE_ERROR(skills.back().fromXml(*it));
}
life = stats.life;
return SUCCESS;
}
#else
TestClass(const Xml& source) :
id(source.getAttribute("id")),
description(source.getChild("description")->text),
stats(*source.getChild("stats")),
appearance(*source.getChild("appearance")),
life(stats.life) {
for (auto& it : source.children)
if (it->name == "skill")
skills.emplace_back(std::move(*it));
}
#endif
struct LocationUpdate {
std::array<char, 16> name;
int x;
int y;
};
struct LifeUpdate {
std::array<char, 16> name;
int newLife;
};
struct LastAttackUpdate {
std::array<char, 16> name;
unsigned int lastAttackTime;
};
int difference(int first, int second) {
if (first > second)
return first - second;
else
return second - first;
}
#ifdef USE_ERROR_CODES
ErrorType updateLocation(const LocationUpdate& update) {
if (difference(x, update.x) > 256 || difference(y, update.y) > 256)
return INVALID_PACKET;
x = update.x;
y = update.y;
return SUCCESS;
}
ErrorType updateLife(const LifeUpdate& update) {
if (update.newLife < 0 || update.newLife > 2048)
return INVALID_PACKET;
life = update.newLife;
return SUCCESS;
}
ErrorType updateLastAttack(const LastAttackUpdate& update) {
if (update.lastAttackTime > now())
return INVALID_PACKET;
lastAttack = update.lastAttackTime;
return SUCCESS;
}
#else
void updateLocation(const LocationUpdate& update) {
if (difference(x, update.x) > 256 || difference(y, update.y) > 256)
NETWORK_ERROR_OR_ABORT("Invalid location update");
x = update.x;
y = update.y;
}
void updateLife(const LifeUpdate& update) {
if (update.newLife < 0 || update.newLife > 2048)
NETWORK_ERROR_OR_ABORT("Invalid life update");
life = update.newLife;
}
void updateLastAttack(const LastAttackUpdate& update) {
if (update.lastAttackTime > now())
NETWORK_ERROR_OR_ABORT("Invalid last attack update");
lastAttack = update.lastAttackTime;
}
#endif
};
// The rest of test 3
template <auto identifier, typename MessageType, typename Reaction>
#ifdef USE_ERROR_CODES
ErrorType parseMessage(const char*& data, int& dataLeft, const Reaction& reaction) {
#else
bool parseMessage(const char*& data, int& dataLeft, const Reaction& reaction) {
#endif
if (*data == identifier) {
if (dataLeft < sizeof(MessageType) + 1) {
#ifdef USE_ERROR_CODES
return NOT_ENOUGH_DATA;
#else
return true;
#endif
}
auto moveData = [&] (int amount) {
data += amount;
dataLeft -= amount;
};
moveData(1);
MessageType message;
memcpy(reinterpret_cast<char*>(&message), data, sizeof(MessageType));
moveData(sizeof(MessageType));
#ifdef USE_ERROR_CODES
return reaction(message);
#else
reaction(message);
#endif
}
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return false;
#endif
}
#ifdef USE_ERROR_CODES
ErrorType testUpdate(std::vector<TestClass>& state, const char*& data, int& dataLeft) {
#else
bool testUpdate(std::vector<TestClass>& state, const char*& data, int& dataLeft) {
#endif
if (dataLeft == 0) {
#ifdef USE_ERROR_CODES
return NOT_ENOUGH_DATA;
#else
return true;
#endif
}
enum MessageIdentifier : char {
LOCATION_UPDATE = 1,
LIFE_UPDATE = 2,
LAST_ATTACK_UPDATE = 3
};
auto match = [&] (auto call) {
return [call, &state] (auto& message) {
for (auto& it : state)
if (!strcmp(it.id.c_str(), message.name.data())) {
#ifdef USE_ERROR_CODES
return (it.*call)(message);
#else
(it.*call)(message);
return;
#endif
}
#ifdef USE_ERROR_CODES
return INVALID_PACKET;
#else
NETWORK_ERROR_OR_ABORT("Referring to nonexistent entity");
#endif
};
};
auto problem = parseMessage<LOCATION_UPDATE, TestClass::LocationUpdate>(data, dataLeft, match(&TestClass::updateLocation));
if (problem) return problem;
problem = parseMessage<LIFE_UPDATE, TestClass::LifeUpdate>(data, dataLeft, match(&TestClass::updateLife));
if (problem) return problem;
problem = parseMessage<LAST_ATTACK_UPDATE, TestClass::LastAttackUpdate>(data, dataLeft, match(&TestClass::updateLastAttack));
if (problem) return problem;
#ifdef USE_ERROR_CODES
return SUCCESS;
#else
return false;
#endif
}
// Overall testing code
int main(int argc, char** argv)
{
if (argc != 3) {
std::cout << "Use " << argv[0] << " XML_FILE_NAME BINARY_FILE_NAME" << std::endl;
return 1;
}
std::ifstream file(argv[1]);
if (!file.good()) {
#ifdef USE_ERROR_CODES
std::cerr << "File not readable" << std::endl;
return 2;
#else
#ifdef __cpp_exceptions
throw std::runtime_error("Could not read XML file");
#else
{ std::cout << "Could not read XML file" << std::endl; exit(3); }
#endif
#endif
}
std::string fileString((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
constexpr int parsingRepeats = 1000;
std::vector<std::shared_ptr<Xml>> parsed;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < parsingRepeats; i++) {
const char* str = fileString.c_str();
parsed.clear();
#ifdef USE_ERROR_CODES
auto result = parseXmlDocument(str, parsed);
// for (auto& it : parsed) {
// std::cout << *it;
// }
#else
#ifdef __cpp_exceptions
try {
#endif
parsed = parseXmlDocument(str);
// for (auto& it : parsed) {
// std::cout << *it;
// }
#ifdef __cpp_exceptions
} catch (std::exception& e) {
std::cerr << "Parsing failed: " << e.what() << std::endl;
}
#endif
#endif
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Parsing took on average " << (duration / parsingRepeats) << " us" << std::endl;
constexpr int fillingRepeats = 10000;
std::vector<TestClass> filled;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < fillingRepeats; i++) {
filled.clear();
#ifdef USE_ERROR_CODES
ErrorType problem;
for (auto& it : parsed) {
filled.emplace_back();
problem = filled.back().fromXml(*it);
if (problem) {
// std::cout << "Invalid XML" << std::endl;
break;
}
}
#else
#ifdef __cpp_exceptions
try {
#endif
for (auto& it : parsed) {
filled.emplace_back(std::move(*it));
}
#ifdef __cpp_exceptions
} catch (std::exception& e) {
std::cerr << "Filling failed: " << e.what() << std::endl;
}
#endif
#endif
}
end = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Filling took on average " << (float(duration) / fillingRepeats) << " us" << std::endl;
constexpr int updates = 1000;
std::vector<TestClass> updated;
std::ifstream updateStream(argv[2], std::ios::binary);
std::vector<char> fileStream((std::istreambuf_iterator<char>(updateStream)), std::istreambuf_iterator<char>());
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < updates; i++) {
updated = filled;
const char* data = fileStream.data();
int dataLeft = fileStream.size();
#ifdef USE_ERROR_CODES
ErrorType problem;
while (!(problem = testUpdate(updated, data, dataLeft))) { }
if (problem != NOT_ENOUGH_DATA) {
// std::cerr << "Error " << problem << std::endl;
}
#else
#ifdef __cpp_exceptions
try {
#endif
while (!testUpdate(updated, data, dataLeft)) { }
#ifdef __cpp_exceptions
} catch (std::exception& e) {
std::cerr << "Updating failed: " << e.what() << std::endl;
}
#endif
#endif
}
end = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Updating took on average " << (float(duration) / updates) << " us" << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment