Last active
September 20, 2018 16:31
-
-
Save cwfitzgerald/3f6fffe24a96abfd0efcdb551822597e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Loading C:\Users\connor\Downloads\models\lucy.obj. | |
Duration: 640.7ms | |
Verts: 14027872 | |
Faces: 28055728 | |
Verts/s: 21895759.2 | |
Faces/s: 43791493.5 | |
MB/s: 1841.7 | |
*/ | |
#include <array> | |
#include <chrono> | |
#include <fstream> | |
#include <future> | |
#include <iomanip> | |
#include <iostream> | |
#include <thread> | |
#include <tuple> | |
#include <utility> | |
#include <vector> | |
#define WIN32_LEAN_AND_MEAN | |
#include <Windows.h> | |
std::pair<char const*, std::size_t> open_file(std::string const& filename) { | |
auto const file_handle = CreateFileA(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); // Leaked it | |
auto const mapping_handle = CreateFileMappingA(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); // Leaked it | |
void* const ptr = MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, 0); | |
MEMORY_BASIC_INFORMATION info; | |
VirtualQuery(ptr, &info, sizeof(info)); | |
return std::make_pair(static_cast<char const *>(ptr), info.RegionSize); | |
} | |
constexpr std::uint16_t combine_bytes(std::uint8_t const a, std::uint8_t const b) { | |
return a << 8 | b; | |
} | |
constexpr std::array<double, 1 << 16> generate_double_array() { | |
std::array<double, 1 << 16> ret{}; | |
for (uint8_t b1 = '0'; b1 <= '9'; ++b1) { | |
for (uint16_t b2 = 0; b2 < 256; ++b2) { | |
ret[combine_bytes(b1, static_cast<uint8_t>(b2))] = (b1 - '0'); | |
} | |
} | |
for (uint8_t b1 = '0'; b1 <= '9'; ++b1) { | |
for (uint8_t b2 = '0'; b2 <= '9'; ++b2) { | |
ret[combine_bytes(b1, b2)] = (b1 - '0') * 10 + (b2 - '0'); | |
} | |
} | |
return ret; | |
} | |
constexpr std::array<double, 1 << 16> double_array = generate_double_array(); | |
bool is_digit(char const c) { | |
return ('0' <= c && c <= '9'); | |
} | |
bool is_number(char c) { | |
return is_digit(c) || c == '-' || c == '.'; | |
} | |
namespace custom { | |
float strtof(char const * const input, char ** const end) { | |
constexpr std::array<double, 8> inverted_powers = { | |
0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 0.00000001 | |
}; | |
std::size_t index = 0; | |
bool negation = false; | |
if (input[index] == '-') { | |
negation = true; | |
index++; | |
} | |
double number = 0.0; | |
while (input[index] != '\0' && input[index + 1] != '\0') { | |
if (is_digit(input[index])) { | |
double const converted = double_array[combine_bytes(input[index], input[index + 1])]; | |
if (is_digit(input[index + 1])) { | |
number = (number * 100) + converted; | |
} | |
else { | |
number = (number * 10) + converted; | |
index += 1; | |
break; | |
} | |
} | |
else { | |
break; | |
} | |
index += 2; | |
} | |
std::uint32_t decimal_exponent = 1; | |
if (input[index] == '.') { | |
index += 1; | |
while (input[index] != '\0' && input[index + 1] != '\0') { | |
if (is_digit(input[index])) { | |
if (is_digit(input[index + 1])) { | |
number += double_array[combine_bytes(input[index], input[index + 1])] * inverted_powers[decimal_exponent]; | |
decimal_exponent += 2; | |
} | |
else { | |
number += double_array[combine_bytes(input[index], input[index + 1])] * inverted_powers[--decimal_exponent]; | |
index += 1; | |
break; | |
} | |
if (decimal_exponent == 8) { | |
// skip excess numbers | |
while (is_number(input[index]) && input[index] != '\0') { | |
index += 1; | |
} | |
break; | |
} | |
} | |
else { | |
break; | |
} | |
index += 2; | |
} | |
} | |
*end = const_cast<char*>(input + index); | |
return static_cast<float>(negation ? -number : number); | |
} | |
__forceinline long strtol(char const * const input, char ** const end) { | |
std::size_t index = 0; | |
bool negation = false; | |
if (input[index] == '-') { | |
negation = true; | |
index++; | |
} | |
long number = 0; | |
while (input[index] != '\0') { | |
if (is_digit(input[index])) { | |
number = (number * 10) + (input[index] - '0'); | |
} | |
else { | |
break; | |
} | |
index += 1; | |
} | |
*end = const_cast<char*>(input + index); | |
return negation ? -number : number; | |
} | |
} | |
struct Position { | |
float x; | |
float y; | |
float z; | |
}; | |
struct Color { | |
float r; | |
float g; | |
float b; | |
}; | |
struct Vertex { | |
Position pos; | |
// Color color; | |
}; | |
struct ParsedObj { | |
std::vector<Position> vertices; | |
std::vector<int32_t> indices; | |
}; | |
ParsedObj parse_obj(const char* const input, std::size_t const size) { | |
ParsedObj ret; | |
std::size_t index = 0; | |
while (index < size) { | |
// Index points to beginning of line | |
if (input[index] == 'v') { | |
// skip space | |
index += 2; | |
// parse x | |
char * out_ptr; | |
float const x = custom::strtof(input + index, &out_ptr); | |
index = out_ptr - input; | |
// points to space | |
index += 1; | |
// parse y | |
float const y = custom::strtof(input + index, &out_ptr); | |
index = out_ptr - input; | |
// points to space | |
index += 1; | |
// parse z | |
float const z = custom::strtof(input + index, &out_ptr); | |
index = out_ptr - input; | |
if (input[index] == '\r') { | |
index += 1; | |
} | |
ret.vertices.emplace_back(Position{ x, y, z }); | |
} | |
else if (input[index] == 'f') { | |
// skip space | |
index += 2; | |
// parse 1 | |
char * out_ptr; | |
int32_t const x = custom::strtol(input + index, &out_ptr) + 1; | |
index = out_ptr - input; | |
// points to space | |
index += 1; | |
// parse 2 | |
int32_t const y = custom::strtol(input + index, &out_ptr) + 1; | |
index = out_ptr - input; | |
// points to space | |
index += 1; | |
// parse 3 | |
int32_t const z = custom::strtol(input + index, &out_ptr) + 1; | |
index = out_ptr - input; | |
if (input[index] == '\r') { | |
index += 1; | |
} | |
ret.indices.emplace_back(x); | |
ret.indices.emplace_back(y); | |
ret.indices.emplace_back(z); | |
} | |
else { | |
while (index != size - 1 && input[index] != '\n') index++; | |
} | |
// All paths return with index pointing to the newline | |
index += 1; | |
} | |
return ret; | |
} | |
std::vector<std::size_t> distribution(std::size_t start, std::size_t end, std::size_t count) { | |
if (count == 1) { | |
return { start, end }; | |
} | |
std::vector<std::size_t> ret; | |
ret.reserve(count + 1); | |
ret.push_back(start); | |
for(std::size_t i = 1; i < count; ++i) { | |
ret.push_back(((end - start) / count) * i); | |
} | |
ret.push_back(end); | |
return ret; | |
} | |
ParsedObj merge(std::vector<ParsedObj> const& args) { | |
std::size_t vert_count = 0; | |
std::size_t index_count = 0; | |
for (auto& arg : args) { | |
vert_count += arg.vertices.size(); | |
index_count += arg.indices.size(); | |
} | |
ParsedObj ret; | |
ret.vertices.resize(vert_count); | |
ret.indices.resize(index_count); | |
auto vit = ret.vertices.begin(); | |
auto iit = ret.indices.begin(); | |
for (auto& arg : args) { | |
vit = std::copy(arg.vertices.begin(), arg.vertices.end(), vit); | |
iit = std::copy(arg.indices.begin(), arg.indices.end(), iit); | |
} | |
return ret; | |
} | |
int main(int argc, char** argv) { | |
if (argc < 2) { | |
std::cerr << "Must pass filename on command line.\n"; | |
exit(1); | |
} | |
std::size_t const tcount = std::thread::hardware_concurrency(); | |
std::cout << "Loading " << argv[1] << ".\n"; | |
auto const start = std::chrono::high_resolution_clock::now(); | |
const char * base_ptr; | |
std::size_t size; | |
std::tie(base_ptr, size) = open_file(argv[1]); | |
auto pairs = distribution(0, size, tcount); | |
std::vector<std::future<ParsedObj>> futures; | |
std::vector<ParsedObj> values; | |
futures.reserve(tcount); | |
values.reserve(tcount); | |
for(std::size_t i = 0; i < tcount; ++i) { | |
futures.push_back(std::async(std::launch::async, parse_obj, base_ptr + pairs[i], pairs[i + 1] - pairs[i])); | |
} | |
for (auto& future : futures) { | |
values.emplace_back(std::move(future.get())); | |
} | |
auto result = merge(values); | |
auto const end = std::chrono::high_resolution_clock::now(); | |
auto duration = end - start; | |
double const milliseconds = duration.count() / 1'000'000.0; | |
double const seconds = duration.count() / 1'000'000'000.0; | |
std::size_t const verts = result.vertices.size(); | |
std::size_t const faces = result.indices.size() / 3; | |
double const megabytes = size / static_cast<float>(1024 * 1024); | |
std::cout << " Duration: " << std::fixed << std::setprecision(1) << milliseconds << "ms\n"; | |
std::cout << " Verts: " << verts << "\n"; | |
std::cout << " Faces: " << faces << "\n"; | |
std::cout << " Verts/s: " << verts / seconds << "\n"; | |
std::cout << " Faces/s: " << faces / seconds << "\n"; | |
std::cout << " MB/s: " << megabytes / seconds << "\n"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment