Replaced UUID storage from str to array of bytes
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
add_executable(bench-core
|
||||
Core.cpp
|
||||
TraceLoggerBench.cpp
|
||||
UUIDBench.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(bench-core PRIVATE
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
namespace lms::core::benchs
|
||||
{
|
||||
static void BM_UUID_fromString(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
benchmark::DoNotOptimize(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc"));
|
||||
}
|
||||
|
||||
static void BM_UUID_fromString_invalid(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
benchmark::DoNotOptimize(UUID::fromString("not-a-valid-uuid-string-at-all-xx"));
|
||||
}
|
||||
|
||||
static void BM_UUID_toString(benchmark::State& state)
|
||||
{
|
||||
const UUID uuid{ *UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") };
|
||||
for (auto _ : state)
|
||||
benchmark::DoNotOptimize(uuid.toString());
|
||||
}
|
||||
|
||||
static void BM_UUID_generate(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
benchmark::DoNotOptimize(UUID::generate());
|
||||
}
|
||||
|
||||
BENCHMARK(BM_UUID_fromString);
|
||||
BENCHMARK(BM_UUID_fromString_invalid);
|
||||
BENCHMARK(BM_UUID_toString);
|
||||
BENCHMARK(BM_UUID_generate);
|
||||
} // namespace lms::core::benchs
|
||||
+60
-39
@@ -19,72 +19,93 @@
|
||||
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
namespace stringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<UUID>
|
||||
readAs(std::string_view str)
|
||||
std::optional<UUID> readAs(std::string_view str)
|
||||
{
|
||||
return UUID::fromString(str);
|
||||
}
|
||||
} // namespace stringUtils
|
||||
|
||||
namespace
|
||||
{
|
||||
bool stringIsUUID(std::string_view str)
|
||||
{
|
||||
static const std::regex re{ R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})" };
|
||||
// Each entry is the str index of the high hex char for that UUID byte
|
||||
constexpr std::array<std::size_t, 16> byteOffsets{
|
||||
0, 2, 4, 6, // group 1 (4 bytes, positions 0-7)
|
||||
9, 11, // group 2 (2 bytes, positions 9-12)
|
||||
14, 16, // group 3 (2 bytes, positions 14-17)
|
||||
19, 21, // group 4 (2 bytes, positions 19-22)
|
||||
24, 26, 28, 30, 32, 34 // group 5 (6 bytes, positions 24-35)
|
||||
};
|
||||
|
||||
return std::regex_match(std::cbegin(str), std::cend(str), re);
|
||||
bool parseUUID(std::string_view str, std::array<std::byte, 16>& out)
|
||||
{
|
||||
if (str.size() != 36 || str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-')
|
||||
return false;
|
||||
|
||||
for (std::size_t i{}; i < 16; ++i)
|
||||
{
|
||||
unsigned int byte{};
|
||||
const char* begin{ str.data() + byteOffsets[i] };
|
||||
const auto [ptr, ec]{ std::from_chars(begin, begin + 2, byte, 16) };
|
||||
if (ec != std::errc{} || ptr != begin + 2)
|
||||
return false;
|
||||
out[i] = static_cast<std::byte>(byte);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UUID::UUID(std::string_view str)
|
||||
: _value{ stringUtils::stringToLower(str) }
|
||||
UUID::UUID(std::array<std::byte, 16> bytes) noexcept
|
||||
: _bytes{ bytes }
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<UUID> UUID::fromString(std::string_view str)
|
||||
{
|
||||
if (!stringIsUUID(str))
|
||||
std::array<std::byte, 16> bytes{};
|
||||
if (!parseUUID(str, bytes))
|
||||
return std::nullopt;
|
||||
return UUID{ bytes };
|
||||
}
|
||||
|
||||
return UUID{ str };
|
||||
UUID UUID::fromBytes(std::span<const std::byte, 16> bytes) noexcept
|
||||
{
|
||||
std::array<std::byte, 16> arr{};
|
||||
std::copy(bytes.begin(), bytes.end(), arr.begin());
|
||||
return UUID{ arr };
|
||||
}
|
||||
|
||||
std::string UUID::toString() const
|
||||
{
|
||||
static constexpr char hex[]{ "0123456789abcdef" };
|
||||
std::string s(36, '-');
|
||||
for (std::size_t i{}; i < 16; ++i)
|
||||
{
|
||||
const auto b{ std::to_integer<unsigned char>(_bytes[i]) };
|
||||
s[byteOffsets[i]] = hex[b >> 4];
|
||||
s[byteOffsets[i] + 1] = hex[b & 0x0F];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
UUID UUID::generate()
|
||||
{
|
||||
// Form is "123e4567-e89b-12d3-a456-426614174000"
|
||||
// TODO: store 128 bits and only convert to string when necessary
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) {
|
||||
for (std::size_t i{}; i < byteCount; ++i)
|
||||
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(random::getRandom<std::uint8_t>(0, 255));
|
||||
} };
|
||||
|
||||
concatRandomBytes(oss, 4);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 6);
|
||||
|
||||
const auto uuid{ fromString(oss.str()) };
|
||||
assert(uuid);
|
||||
return uuid.value();
|
||||
std::uniform_int_distribution<std::uint8_t> dist{ 0, 255 };
|
||||
auto& rng{ random::getRandGenerator() };
|
||||
std::array<std::byte, binarySize> bytes{};
|
||||
for (auto& b : bytes)
|
||||
b = static_cast<std::byte>(dist(rng));
|
||||
return UUID{ bytes };
|
||||
}
|
||||
} // namespace lms::core
|
||||
} // namespace lms::core
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -31,24 +34,28 @@ namespace lms::core
|
||||
class UUID
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t binarySize{ 16 };
|
||||
|
||||
UUID() noexcept = default;
|
||||
static std::optional<UUID> fromString(std::string_view str);
|
||||
static UUID fromBytes(std::span<const std::byte, binarySize> bytes) noexcept;
|
||||
static UUID generate();
|
||||
|
||||
std::string_view getAsString() const { return _value; }
|
||||
std::string toString() const;
|
||||
std::span<const std::byte, binarySize> bytes() const noexcept { return _bytes; }
|
||||
|
||||
auto operator<=>(const UUID&) const = default;
|
||||
|
||||
private:
|
||||
UUID(std::string_view value);
|
||||
std::string _value;
|
||||
explicit UUID(std::array<std::byte, binarySize> bytes) noexcept;
|
||||
std::array<std::byte, binarySize> _bytes{};
|
||||
};
|
||||
} // namespace lms::core
|
||||
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<UUID>
|
||||
readAs(std::string_view str);
|
||||
std::optional<UUID> readAs(std::string_view str);
|
||||
}
|
||||
|
||||
namespace std
|
||||
@@ -56,9 +63,10 @@ namespace std
|
||||
template<>
|
||||
struct hash<lms::core::UUID>
|
||||
{
|
||||
size_t operator()(const lms::core::UUID& str) const
|
||||
size_t operator()(const lms::core::UUID& uuid) const noexcept
|
||||
{
|
||||
return hash<std::string_view>{}(str.getAsString());
|
||||
const auto& b{ uuid.bytes() };
|
||||
return hash<string_view>{}({ static_cast<const char*>(static_cast<const void*>(b.data())), b.size() });
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -28,6 +25,19 @@
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
TEST(UUID, fromString_invalid)
|
||||
{
|
||||
EXPECT_FALSE(UUID::fromString(""));
|
||||
EXPECT_FALSE(UUID::fromString("not-a-uuid"));
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178f")); // too short
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fcc")); // too long
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7_0693e45178fc")); // wrong separator position 23
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178gz")); // invalid hex char 'g','z'
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839Xbee2-4e9d-a7b7-0693e45178fc")); // dash replaced at position 8
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2X4e9d-a7b7-0693e45178fc")); // dash replaced at position 13
|
||||
EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9dXa7b7-0693e45178fc")); // dash replaced at position 18
|
||||
}
|
||||
|
||||
TEST(UUID, caseInsensitive)
|
||||
{
|
||||
const std::optional<UUID> uuid1{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") };
|
||||
@@ -37,4 +47,51 @@ namespace lms::core
|
||||
EXPECT_TRUE(uuid1 >= uuid2);
|
||||
EXPECT_TRUE(uuid1 <= uuid2);
|
||||
}
|
||||
|
||||
TEST(UUID, toString_roundTrip)
|
||||
{
|
||||
const std::string str{ "3f51c839-bee2-4e9d-a7b7-0693e45178fc" };
|
||||
const std::optional<UUID> uuid{ UUID::fromString(str) };
|
||||
|
||||
ASSERT_TRUE(uuid);
|
||||
EXPECT_EQ(uuid->toString(), str);
|
||||
}
|
||||
|
||||
TEST(UUID, toString_lowercase)
|
||||
{
|
||||
const std::optional<UUID> uuid{ UUID::fromString("3F51C839-BEE2-4E9D-A7B7-0693E45178FC") };
|
||||
|
||||
ASSERT_TRUE(uuid);
|
||||
EXPECT_EQ(uuid->toString(), "3f51c839-bee2-4e9d-a7b7-0693e45178fc");
|
||||
}
|
||||
|
||||
TEST(UUID, fromBytes_roundTrip)
|
||||
{
|
||||
const std::optional<UUID> uuid{ UUID::fromString("550e8400-e29b-41d4-a716-446655440000") };
|
||||
ASSERT_TRUE(uuid);
|
||||
|
||||
const UUID fromB{ UUID::fromBytes(uuid->bytes()) };
|
||||
EXPECT_EQ(fromB, *uuid);
|
||||
EXPECT_EQ(fromB.toString(), "550e8400-e29b-41d4-a716-446655440000");
|
||||
}
|
||||
|
||||
TEST(UUID, bytes_size)
|
||||
{
|
||||
const std::optional<UUID> uuid{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") };
|
||||
ASSERT_TRUE(uuid);
|
||||
EXPECT_EQ(uuid->bytes().size(), 16U);
|
||||
}
|
||||
|
||||
TEST(UUID, generate_validString)
|
||||
{
|
||||
const UUID uuid{ UUID::generate() };
|
||||
const std::string s{ uuid.toString() };
|
||||
|
||||
ASSERT_EQ(s.size(), 36U);
|
||||
EXPECT_EQ(s[8], '-');
|
||||
EXPECT_EQ(s[13], '-');
|
||||
EXPECT_EQ(s[18], '-');
|
||||
EXPECT_EQ(s[23], '-');
|
||||
EXPECT_TRUE(UUID::fromString(s).has_value());
|
||||
}
|
||||
} // namespace lms::core
|
||||
Reference in New Issue
Block a user