Use wyhash for <til/hash.h> (#13686)

wyhash was chosen based on the results found in `smhasher`, were it proved
itself as an algorithm with little flaws and fairly high output quality.

While I have a personal preference for xxhash (XXH3 specifically), wyhash is a
better fit for this project as its source code is multiple magnitudes smaller,
simplifying the review and integration into the header-only `hash.h` file.

For use with hashmaps the hash quality doesn't actually matter much for
optimal performance and instead the binary size usually matters more.
But even in that scenario wyhash is fairly close to FNV1a (aka "FNV64").

The result is that this new hash algorithm will only have little impact on
hashmap performance if used over the standard FNV1a as used in the STL,
while simultaneously offering a vastly better hash quality.

This partially solves #13124.

## Validation Steps Performed
* Added test cases 
This commit is contained in:
Leonard Hecker 2022-08-10 23:11:09 +02:00 committed by GitHub
parent 751db3d114
commit a43d5028b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 441 additions and 78 deletions

View File

@ -1,4 +1,5 @@
ABANDONFONT ABANDONFONT
ABCDEFGHIJKLMNOPQRSTUVWXY
abgr abgr
abi abi
ACCESSTOKEN ACCESSTOKEN
@ -42,7 +43,6 @@ antialias
antialiasing antialiasing
ANull ANull
anycpu anycpu
AOn
APARTMENTTHREADED APARTMENTTHREADED
APCs APCs
api api
@ -80,7 +80,6 @@ ASingle
asm asm
asmv asmv
asmx asmx
AStomps
ASYNCWINDOWPOS ASYNCWINDOWPOS
atch atch
ATest ATest
@ -232,7 +231,6 @@ chcp
checkbox checkbox
checkboxes checkboxes
chh chh
Childitem
chk chk
chrono chrono
CHT CHT
@ -669,6 +667,7 @@ ECH
echokey echokey
ecount ecount
ECpp ECpp
ect
Edgium Edgium
EDITKEYS EDITKEYS
EDITTEXT EDITTEXT
@ -702,6 +701,7 @@ ENUMLOGFONTEX
enumranges enumranges
envvar envvar
eol eol
eplace
EPres EPres
EQU EQU
ERASEBKGND ERASEBKGND
@ -779,7 +779,6 @@ FIXEDCONVERTED
FIXEDFILEINFO FIXEDFILEINFO
Flg Flg
flyout flyout
fmix
fmodern fmodern
fmtarg fmtarg
fmtid fmtid
@ -996,6 +995,7 @@ HPR
HProvider HProvider
HREDRAW HREDRAW
hresult hresult
hrottled
HRSRC HRSRC
hscroll hscroll
hsl hsl
@ -1030,6 +1030,7 @@ ICache
icacls icacls
iccex iccex
IChar IChar
icket
ico ico
IComponent IComponent
ICONERROR ICONERROR
@ -2431,6 +2432,8 @@ uint
uintptr uintptr
ulcch ulcch
ulong ulong
umul
umulh
Unadvise Unadvise
unattend unattend
uncomment uncomment
@ -2735,6 +2738,9 @@ WUX
WVerify WVerify
WWith WWith
wxh wxh
wyhash
wymix
wyr
xact xact
xaml xaml
Xamlmeta Xamlmeta
@ -2795,6 +2801,7 @@ YSize
YSubstantial YSubstantial
YVIRTUALSCREEN YVIRTUALSCREEN
YWalk YWalk
Zabcdefghijklmnopqrstuvwxyz
ZCmd ZCmd
ZCtrl ZCtrl
zsh zsh

View File

@ -276,6 +276,39 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
``` ```
## wyhash
**Source**: [https://github.com/wangyi-fudan/wyhash](https://github.com/wangyi-fudan/wyhash)
### License
```
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
```
## ConEmu ## ConEmu
**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu) **Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu)

25
oss/wyhash/LICENSE Normal file
View File

@ -0,0 +1,25 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@ -0,0 +1,4 @@
### Notes for Future Maintainers
[wyhash](https://github.com/wangyi-fudan/wyhash) is used as the hash algorithm for `<til/hash.h>` and its `til::hasher`.
The source code was directly integrated into that header file and can be found in `/src/inc/til/hash.h`.

View File

@ -0,0 +1,14 @@
{
"Registrations": [
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://github.com/wangyi-fudan/wyhash",
"commitHash": "e77036ac1943369dc03e611cde52a8570f8ceefe"
}
}
}
],
"Version": 1
}

View File

@ -17,7 +17,6 @@ Author(s):
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <til/bit.h>
#include <til/hash.h> #include <til/hash.h>
// std::unordered_map needs help to know how to hash a til::point // std::unordered_map needs help to know how to hash a til::point
@ -33,9 +32,9 @@ namespace std
// - coord - the coord to hash // - coord - the coord to hash
// Return Value: // Return Value:
// - the hashed coord // - the hashed coord
constexpr size_t operator()(const til::point coord) const noexcept size_t operator()(const til::point coord) const noexcept
{ {
return til::hash(til::bit_cast<uint64_t>(coord)); return til::hash(coord);
} }
}; };
} }

View File

@ -3,7 +3,25 @@
#pragma once #pragma once
#include "bit.h" #pragma warning(push)
// std::hash() doesn't test for `nullptr`, nor do we want to.
#pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
// Misdiagnosis: static_cast<const void*> is used to differentiate between 2 overloads of til::hasher::write.
#pragma warning(disable : 26474) // Don't cast between pointer types when the conversion could be implicit (type.1).
// We don't want to unnecessarily modify wyhash from its original.
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26494) // Variable '...' is uninitialized. Always initialize an object (type.5).
#pragma warning(disable : 26496) // The variable '...' does not change after construction, mark it as const (con.4).
#if defined(_M_X64) && !defined(_M_ARM64EC)
#define TIL_HASH_X64
#elif defined(_M_ARM64) || defined(_M_ARM64EC)
#define TIL_HASH_ARM64
#elif defined(_M_IX86)
#define TIL_HASH_X86
#else
#error "Unsupported architecture for til::hash"
#endif
namespace til namespace til
{ {
@ -12,31 +30,27 @@ namespace til
struct hasher struct hasher
{ {
explicit constexpr hasher(size_t state = FNV_offset_basis) noexcept : constexpr hasher() = default;
explicit constexpr hasher(size_t state) noexcept :
_hash{ state } {} _hash{ state } {}
template<typename T> template<typename T>
constexpr void write(const T& v) noexcept hasher& write(const T& v) noexcept
{ {
hash_trait<T>{}(*this, v); hash_trait<T>{}(*this, v);
return *this;
} }
template<typename T, typename = std::enable_if_t<std::has_unique_object_representations_v<T>>> template<typename T, typename = std::enable_if_t<std::has_unique_object_representations_v<T>>>
constexpr void write(const T* data, size_t count) noexcept hasher& write(const T* data, size_t count) noexcept
{ {
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). return write(static_cast<const void*>(data), sizeof(T) * count);
write(reinterpret_cast<const uint8_t*>(data), sizeof(T) * count);
} }
#pragma warning(suppress : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23). hasher& write(const void* data, size_t len) noexcept
constexpr void write(const uint8_t* data, size_t count) noexcept
{ {
for (size_t i = 0; i < count; ++i) _hash = _wyhash(data, len, _hash);
{ return *this;
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
_hash ^= static_cast<size_t>(data[i]);
_hash *= FNV_prime;
}
} }
constexpr size_t finalize() const noexcept constexpr size_t finalize() const noexcept
@ -45,15 +59,151 @@ namespace til
} }
private: private:
#if defined(_WIN64) #if defined(TIL_HASH_X86)
static constexpr size_t FNV_offset_basis = 14695981039346656037ULL;
static constexpr size_t FNV_prime = 1099511628211ULL;
#else
static constexpr size_t FNV_offset_basis = 2166136261U;
static constexpr size_t FNV_prime = 16777619U;
#endif
size_t _hash = FNV_offset_basis; static uint32_t _wyr24(const uint8_t* p, uint32_t k) noexcept
{
return static_cast<uint32_t>(p[0]) << 16 | static_cast<uint32_t>(p[k >> 1]) << 8 | p[k - 1];
}
static uint32_t _wyr32(const uint8_t* p) noexcept
{
uint32_t v;
memcpy(&v, p, 4);
return v;
}
static void _wymix32(uint32_t* a, uint32_t* b) noexcept
{
uint64_t c = *a ^ UINT32_C(0x53c5ca59);
c *= *b ^ UINT32_C(0x74743c1b);
*a = static_cast<uint32_t>(c);
*b = static_cast<uint32_t>(c >> 32);
}
static uint32_t _wyhash(const void* data, uint32_t len, uint32_t seed) noexcept
{
auto p = static_cast<const uint8_t*>(data);
auto i = len;
auto see1 = len;
_wymix32(&seed, &see1);
for (; i > 8; i -= 8, p += 8)
{
seed ^= _wyr32(p);
see1 ^= _wyr32(p + 4);
_wymix32(&seed, &see1);
}
if (i >= 4)
{
seed ^= _wyr32(p);
see1 ^= _wyr32(p + i - 4);
}
else if (i)
{
seed ^= _wyr24(p, i);
}
_wymix32(&seed, &see1);
_wymix32(&seed, &see1);
return seed ^ see1;
}
#else // defined(TIL_HASH_X86)
static uint64_t _wyr3(const uint8_t* p, size_t k) noexcept
{
return (static_cast<uint64_t>(p[0]) << 16) | (static_cast<uint64_t>(p[k >> 1]) << 8) | p[k - 1];
}
static uint64_t _wyr4(const uint8_t* p) noexcept
{
uint32_t v;
memcpy(&v, p, 4);
return v;
}
static uint64_t _wyr8(const uint8_t* p) noexcept
{
uint64_t v;
memcpy(&v, p, 8);
return v;
}
static uint64_t _wymix(uint64_t lhs, uint64_t rhs) noexcept
{
#if defined(TIL_HASH_X64)
uint64_t hi;
uint64_t lo = _umul128(lhs, rhs, &hi);
#elif defined(TIL_HASH_ARM64)
const uint64_t lo = lhs * rhs;
const uint64_t hi = __umulh(lhs, rhs);
#endif
return lo ^ hi;
}
static uint64_t _wyhash(const void* data, uint64_t len, uint64_t seed) noexcept
{
static constexpr auto s0 = UINT64_C(0xa0761d6478bd642f);
static constexpr auto s1 = UINT64_C(0xe7037ed1a0b428db);
static constexpr auto s2 = UINT64_C(0x8ebc6af09c88c6e3);
static constexpr auto s3 = UINT64_C(0x589965cc75374cc3);
auto p = static_cast<const uint8_t*>(data);
seed ^= s0;
uint64_t a;
uint64_t b;
if (len <= 16)
{
if (len >= 4)
{
a = (_wyr4(p) << 32) | _wyr4(p + ((len >> 3) << 2));
b = (_wyr4(p + len - 4) << 32) | _wyr4(p + len - 4 - ((len >> 3) << 2));
}
else if (len > 0)
{
a = _wyr3(p, len);
b = 0;
}
else
{
a = b = 0;
}
}
else
{
auto i = len;
if (i > 48)
{
auto seed1 = seed;
auto seed2 = seed;
do
{
seed = _wymix(_wyr8(p) ^ s1, _wyr8(p + 8) ^ seed);
seed1 = _wymix(_wyr8(p + 16) ^ s2, _wyr8(p + 24) ^ seed1);
seed2 = _wymix(_wyr8(p + 32) ^ s3, _wyr8(p + 40) ^ seed2);
p += 48;
i -= 48;
} while (i > 48);
seed ^= seed1 ^ seed2;
}
while (i > 16)
{
seed = _wymix(_wyr8(p) ^ s1, _wyr8(p + 8) ^ seed);
i -= 16;
p += 16;
}
a = _wyr8(p + i - 16);
b = _wyr8(p + i - 8);
}
return _wymix(s1 ^ len, _wymix(a ^ s1, b ^ seed));
}
#endif // defined(TIL_HASH_X86)
size_t _hash = 0;
}; };
namespace details namespace details
@ -61,10 +211,9 @@ namespace til
template<typename T, bool enable> template<typename T, bool enable>
struct conditionally_enabled_hash_trait struct conditionally_enabled_hash_trait
{ {
constexpr void operator()(hasher& h, const T& v) const noexcept void operator()(hasher& h, const T& v) const noexcept
{ {
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). h.write(static_cast<const void*>(&v), sizeof(T));
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
} }
}; };
@ -87,80 +236,55 @@ namespace til
template<> template<>
struct hash_trait<float> struct hash_trait<float>
{ {
constexpr void operator()(hasher& h, float v) const noexcept void operator()(hasher& h, float v) const noexcept
{ {
v = v == 0.0f ? 0.0f : v; // map -0 to 0 v = v == 0.0f ? 0.0f : v; // map -0 to 0
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). h.write(static_cast<const void*>(&v), sizeof(v));
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
} }
}; };
template<> template<>
struct hash_trait<double> struct hash_trait<double>
{ {
constexpr void operator()(hasher& h, double v) const noexcept void operator()(hasher& h, double v) const noexcept
{ {
v = v == 0.0 ? 0.0 : v; // map -0 to 0 v = v == 0.0 ? 0.0 : v; // map -0 to 0
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). h.write(static_cast<const void*>(&v), sizeof(v));
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
} }
}; };
template<typename T, typename CharTraits, typename Allocator> template<typename T, typename CharTraits, typename Allocator>
struct hash_trait<std::basic_string<T, CharTraits, Allocator>> struct hash_trait<std::basic_string<T, CharTraits, Allocator>>
{ {
constexpr void operator()(hasher& h, const std::basic_string<T, CharTraits, Allocator>& v) const noexcept void operator()(hasher& h, const std::basic_string<T, CharTraits, Allocator>& v) const noexcept
{ {
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). h.write(v.data(), v.size());
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
} }
}; };
template<typename T, typename CharTraits> template<typename T, typename CharTraits>
struct hash_trait<std::basic_string_view<T, CharTraits>> struct hash_trait<std::basic_string_view<T, CharTraits>>
{ {
constexpr void operator()(hasher& h, const std::basic_string_view<T, CharTraits>& v) const noexcept void operator()(hasher& h, const std::basic_string_view<T, CharTraits>& v) const noexcept
{ {
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). h.write(v.data(), v.size());
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
} }
}; };
template<typename T> template<typename T>
constexpr size_t hash(const T& v) noexcept size_t hash(const T& v) noexcept
{ {
if constexpr (sizeof(T) <= sizeof(size_t) && (std::is_integral_v<T> || std::is_enum_v<T>)) hasher h;
{ h.write(v);
// This runs murmurhash3's finalizer (fmix32/fmix64) on a single integer. return h.finalize();
// It's fast, public domain and produces good results. }
//
// Using til::as_unsigned here allows the compiler to drop the first inline size_t hash(const void* data, size_t len) noexcept
// `>> 33` mix for all Ts which are >= 32 bits. {
// The existence of sign extension shouldn't change hash quality. hasher h;
size_t h = til::as_unsigned(v); h.write(data, len);
if constexpr (sizeof(size_t) == 4) return h.finalize();
{
h ^= h >> 16;
h *= UINT32_C(0x85ebca6b);
h ^= h >> 13;
h *= UINT32_C(0xc2b2ae35);
h ^= h >> 16;
}
else
{
h ^= h >> 33;
h *= UINT64_C(0xff51afd7ed558ccd);
h ^= h >> 33;
h *= UINT64_C(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
}
return h;
}
else
{
hasher h;
h.write(v);
return h.finalize();
}
} }
} }
#pragma warning(pop)

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <til/hash.h>
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class HashTests
{
TEST_CLASS(HashTests);
TEST_METHOD(TestVectors)
{
struct Test
{
std::string_view input;
size_t seed;
uint64_t expected64;
uint32_t expected32;
};
static constexpr std::array tests{
Test{ "", 0, 0x42bc986dc5eec4d3, 0xa45f982f },
Test{ "a", 1, 0x84508dc903c31551, 0x09021114 },
Test{ "abc", 2, 0x0bc54887cfc9ecb1, 0xfe40215d },
Test{ "message digest", 3, 0x6e2ff3298208a67c, 0x6e0fb730 },
Test{ "abcdefghijklmnopqrstuvwxyz", 4, 0x9a64e42e897195b9, 0x9435b8c2 },
Test{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 5, 0x9199383239c32554, 0xccf9734c },
Test{ "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 6, 0x7c1ccf6bba30f5a5, 0x9fa5ef6e },
};
for (const auto& t : tests)
{
const auto actual = til::hasher{ t.seed }.write(t.input).finalize();
#if defined(TIL_HASH_X86)
VERIFY_ARE_EQUAL(t.expected32, actual);
#else
VERIFY_ARE_EQUAL(t.expected64, actual);
#endif
}
}
};

View File

@ -19,6 +19,7 @@
<ClCompile Include="CoalesceTests.cpp" /> <ClCompile Include="CoalesceTests.cpp" />
<ClCompile Include="ColorTests.cpp" /> <ClCompile Include="ColorTests.cpp" />
<ClCompile Include="EnumSetTests.cpp" /> <ClCompile Include="EnumSetTests.cpp" />
<ClCompile Include="HashTests.cpp" />
<ClCompile Include="MathTests.cpp" /> <ClCompile Include="MathTests.cpp" />
<ClCompile Include="mutex.cpp" /> <ClCompile Include="mutex.cpp" />
<ClCompile Include="OperatorTests.cpp" /> <ClCompile Include="OperatorTests.cpp" />
@ -35,6 +36,32 @@
<ClCompile Include="u8u16convertTests.cpp" /> <ClCompile Include="u8u16convertTests.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\inc\til\at.h" />
<ClInclude Include="..\..\inc\til\atomic.h" />
<ClInclude Include="..\..\inc\til\bit.h" />
<ClInclude Include="..\..\inc\til\bitmap.h" />
<ClInclude Include="..\..\inc\til\coalesce.h" />
<ClInclude Include="..\..\inc\til\color.h" />
<ClInclude Include="..\..\inc\til\enumset.h" />
<ClInclude Include="..\..\inc\til\hash.h" />
<ClInclude Include="..\..\inc\til\latch.h" />
<ClInclude Include="..\..\inc\til\math.h" />
<ClInclude Include="..\..\inc\til\mutex.h" />
<ClInclude Include="..\..\inc\til\operators.h" />
<ClInclude Include="..\..\inc\til\pmr.h" />
<ClInclude Include="..\..\inc\til\point.h" />
<ClInclude Include="..\..\inc\til\rand.h" />
<ClInclude Include="..\..\inc\til\rect.h" />
<ClInclude Include="..\..\inc\til\replace.h" />
<ClInclude Include="..\..\inc\til\rle.h" />
<ClInclude Include="..\..\inc\til\size.h" />
<ClInclude Include="..\..\inc\til\some.h" />
<ClInclude Include="..\..\inc\til\spsc.h" />
<ClInclude Include="..\..\inc\til\static_map.h" />
<ClInclude Include="..\..\inc\til\string.h" />
<ClInclude Include="..\..\inc\til\throttled_func.h" />
<ClInclude Include="..\..\inc\til\ticket_lock.h" />
<ClInclude Include="..\..\inc\til\u8u16convert.h" />
<ClInclude Include="..\precomp.h" /> <ClInclude Include="..\precomp.h" />
</ItemGroup> </ItemGroup>
<ItemDefinitionGroup> <ItemDefinitionGroup>

View File

@ -24,8 +24,92 @@
<ClCompile Include="string.cpp" /> <ClCompile Include="string.cpp" />
<ClCompile Include="throttled_func.cpp" /> <ClCompile Include="throttled_func.cpp" />
<ClCompile Include="u8u16convertTests.cpp" /> <ClCompile Include="u8u16convertTests.cpp" />
<ClCompile Include="HashTests.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\precomp.h" /> <ClInclude Include="..\precomp.h" />
<ClInclude Include="..\..\inc\til\at.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\atomic.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\bit.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\bitmap.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\coalesce.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\color.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\enumset.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\hash.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\latch.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\math.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\mutex.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\operators.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\pmr.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\point.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\rand.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\rect.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\replace.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\rle.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\size.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\some.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\spsc.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\static_map.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\string.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\throttled_func.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\ticket_lock.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\til\u8u16convert.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="inc">
<UniqueIdentifier>{7cf29ba4-d33d-4c3b-82e3-ab73e5a79685}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
</Project> </Project>