diff --git a/source/Makefile b/source/Makefile index e7ca2e94a..c485a3989 100644 --- a/source/Makefile +++ b/source/Makefile @@ -341,6 +341,7 @@ SOURCES = \ tt.cpp \ movepick.cpp \ timeman.cpp \ + memory.cpp \ book/book.cpp \ book/apery_book.cpp \ extra/bitop.cpp \ diff --git a/source/YaneuraOu.vcxproj b/source/YaneuraOu.vcxproj index 77615a593..f4087b1b6 100644 --- a/source/YaneuraOu.vcxproj +++ b/source/YaneuraOu.vcxproj @@ -664,6 +664,7 @@ + @@ -733,6 +734,7 @@ + diff --git a/source/YaneuraOu.vcxproj.filters b/source/YaneuraOu.vcxproj.filters index d1dd3a46c..c5e330a46 100644 --- a/source/YaneuraOu.vcxproj.filters +++ b/source/YaneuraOu.vcxproj.filters @@ -352,6 +352,9 @@ リソース ファイル\eval\nnue\architectures + + リソース ファイル + @@ -567,6 +570,9 @@ リソース ファイル\engine\dlshogi-engine + + リソース ファイル + diff --git a/source/config.h b/source/config.h index 798fbddbc..eb954baa2 100644 --- a/source/config.h +++ b/source/config.h @@ -659,6 +659,9 @@ extern GlobalOptions_ GlobalOptions; #define ASSERT_LV4(X) ASSERT_LV_EX(4, X) #define ASSERT_LV5(X) ASSERT_LV_EX(5, X) +// memoryがalignされているかのassert +#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + // --- declaration of unreachablity // switchにおいてdefaultに到達しないことを明示して高速化させる diff --git a/source/eval/nnue/evaluate_nnue.cpp b/source/eval/nnue/evaluate_nnue.cpp index 67f61885f..f2b785ab4 100644 --- a/source/eval/nnue/evaluate_nnue.cpp +++ b/source/eval/nnue/evaluate_nnue.cpp @@ -8,7 +8,7 @@ #include "../../evaluate.h" #include "../../position.h" -#include "../../misc.h" +#include "../../memory.h" #include "../../usi.h" #if defined(USE_EVAL_HASH) @@ -17,6 +17,8 @@ #include "evaluate_nnue.h" +using namespace Stockfish; + namespace Eval { namespace NNUE { @@ -24,7 +26,7 @@ namespace Eval { int FV_SCALE = 16; // 水匠5では24がベストらしいのでエンジンオプション"FV_SCALE"で変更可能にした。 // 入力特徴量変換器 - AlignedPtr feature_transformer; + LargePagePtr feature_transformer; // 評価関数 AlignedPtr network; @@ -35,7 +37,7 @@ namespace Eval { // 評価関数の構造を表す文字列を取得する std::string GetArchitectureString() { return "Features=" + FeatureTransformer::GetStructureString() + - ",Network=" + Network::GetStructureString(); + ",Network=" + Network::GetStructureString(); } namespace { @@ -45,14 +47,15 @@ namespace Eval { // 評価関数パラメータを初期化する template void Initialize(AlignedPtr& pointer) { - - // → メモリはLarge Pageから確保することで高速化する。 - void* ptr = LargeMemory::static_alloc(sizeof(T) , alignof(T), true); - pointer.reset(reinterpret_cast(ptr)); - - //sync_cout << "nnue.alloc(" << sizeof(T) << "," << alignof(T) << ")" << sync_endl; + pointer = make_unique_aligned(); } + template + void Initialize(LargePagePtr& pointer) { + // → メモリはLarge Pageから確保することで高速化する。 + pointer = make_unique_large_page(); + } + // 評価関数パラメータを読み込む template Tools::Result ReadParameters(std::istream& stream, const AlignedPtr& pointer) { @@ -63,7 +66,17 @@ namespace Eval { return pointer->ReadParameters(stream); } - // 評価関数パラメータを書き込む + // 評価関数パラメータを読み込む + template + Tools::Result ReadParameters(std::istream& stream, const LargePagePtr& pointer) { + std::uint32_t header; + stream.read(reinterpret_cast(&header), sizeof(header)); + if (!stream) return Tools::ResultCode::FileReadError; + if (header != T::GetHashValue()) return Tools::ResultCode::FileMismatch; + return pointer->ReadParameters(stream); + } + + // 評価関数パラメータを書き込む template bool WriteParameters(std::ostream& stream, const AlignedPtr& pointer) { constexpr std::uint32_t header = T::GetHashValue(); @@ -71,12 +84,21 @@ namespace Eval { return pointer->WriteParameters(stream); } + // 評価関数パラメータを書き込む + template + bool WriteParameters(std::ostream& stream, const LargePagePtr& pointer) { + constexpr std::uint32_t header = T::GetHashValue(); + stream.write(reinterpret_cast(&header), sizeof(header)); + return pointer->WriteParameters(stream); + } + + } // namespace Detail // 評価関数パラメータを初期化する void Initialize() { - Detail::Initialize(feature_transformer); - Detail::Initialize(network); + Detail::Initialize(feature_transformer); + Detail::Initialize(network); } } // namespace @@ -112,16 +134,16 @@ namespace Eval { Tools::Result result = ReadHeader(stream, &hash_value, &architecture); if (result.is_not_ok()) return result; if (hash_value != kHashValue) return Tools::ResultCode::FileMismatch; - result = Detail::ReadParameters(stream, feature_transformer); if (result.is_not_ok()) return result; - result = Detail::ReadParameters(stream, network); if (result.is_not_ok()) return result; + result = Detail::ReadParameters(stream, feature_transformer); if (result.is_not_ok()) return result; + result = Detail::ReadParameters(stream, network); if (result.is_not_ok()) return result; return (stream && stream.peek() == std::ios::traits_type::eof()) ? Tools::ResultCode::Ok : Tools::ResultCode::FileCloseError; } // 評価関数パラメータを書き込む bool WriteParameters(std::ostream& stream) { if (!WriteHeader(stream, kHashValue, GetArchitectureString())) return false; - if (!Detail::WriteParameters(stream, feature_transformer)) return false; - if (!Detail::WriteParameters(stream, network)) return false; + if (!Detail::WriteParameters(stream, feature_transformer)) return false; + if (!Detail::WriteParameters(stream, network)) return false; return !stream.fail(); } diff --git a/source/eval/nnue/evaluate_nnue.h b/source/eval/nnue/evaluate_nnue.h index 50a649902..f8cec9273 100644 --- a/source/eval/nnue/evaluate_nnue.h +++ b/source/eval/nnue/evaluate_nnue.h @@ -10,9 +10,8 @@ #include "nnue_feature_transformer.h" #include "nnue_architecture.h" -#include "../../misc.h" - -#include +//#include "../../misc.h" +#include "../../memory.h" // 評価関数のソースコードへの埋め込みをする時は、EVAL_EMBEDDINGをdefineして、 // ⇓この2つのシンボルを正しく定義するembedded_nnue.cppを書けば良い。 @@ -31,30 +30,11 @@ namespace Eval::NNUE { constexpr std::uint32_t kHashValue = FeatureTransformer::GetHashValue() ^ Network::GetHashValue(); - // Deleter for automating release of memory area - // メモリ領域の解放を自動化するためのデリータ - template - struct LargeMemoryDeleter { - - void operator()(T* ptr) const { - - // Tクラスのデストラクタ - ptr->~T(); - - // このメモリはLargeMemoryクラスを利用して確保したものなので、 - // このクラスのfree()を呼び出して開放する。 - LargeMemory::static_free(ptr); - } - }; - - template - using AlignedPtr = std::unique_ptr>; - // 入力特徴量変換器 - extern AlignedPtr feature_transformer; + extern Stockfish::LargePagePtr feature_transformer; // 評価関数 - extern AlignedPtr network; + extern Stockfish::AlignedPtr network; // 評価関数ファイル名 extern const char* const kFileName; diff --git a/source/eval/nnue/layers/affine_transform.h b/source/eval/nnue/layers/affine_transform.h index 4fc7e9d95..af7f4dee3 100644 --- a/source/eval/nnue/layers/affine_transform.h +++ b/source/eval/nnue/layers/affine_transform.h @@ -150,7 +150,7 @@ class AffineTransform { for (std::size_t i = 0; i < kOutputDimensions; ++i) biases_[i] = read_little_endian(stream); for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) - weights_[get_weight_index(i)] = read_little_endian(stream); + weights_[get_weight_index(IndexType(i))] = read_little_endian(stream); return !stream.fail() ? Tools::ResultCode::Ok : Tools::ResultCode::FileReadError; } diff --git a/source/eval/nnue/layers/affine_transform_sparse_input.h b/source/eval/nnue/layers/affine_transform_sparse_input.h index 7c6725c23..ffcb6101d 100644 --- a/source/eval/nnue/layers/affine_transform_sparse_input.h +++ b/source/eval/nnue/layers/affine_transform_sparse_input.h @@ -178,7 +178,7 @@ class AffineTransformSparseInput { for (std::size_t i = 0; i < kOutputDimensions; ++i) biases_[i] = read_little_endian(stream); for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) - weights_[get_weight_index(i)] = read_little_endian(stream); + weights_[get_weight_index(IndexType(i))] = read_little_endian(stream); return !stream.fail() ? Tools::ResultCode::Ok : Tools::ResultCode::FileReadError; } diff --git a/source/memory.cpp b/source/memory.cpp new file mode 100644 index 000000000..0c105b96b --- /dev/null +++ b/source/memory.cpp @@ -0,0 +1,276 @@ +#include "memory.h" +#include "usi.h" + +#include + +#if __has_include("features.h") + #include +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + #include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include +#endif + +#ifdef _WIN32 + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #include // std::hex, std::dec + #include // std::cerr + #include // std::endl + #include + +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. + +extern "C" { +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +} +#endif + + +namespace Stockfish { + +// Wrappers for systems where the c++17 implementation does not guarantee the +// availability of aligned_alloc(). Memory allocated with std_aligned_alloc() +// must be freed with std_aligned_free(). + +void* std_aligned_alloc(size_t alignment, size_t size) { +#if defined(_ISOC11_SOURCE) + return aligned_alloc(alignment, size); +#elif defined(POSIXALIGNEDALLOC) + void* mem = nullptr; + posix_memalign(&mem, alignment, size); + return mem; +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + return _mm_malloc(size, alignment); +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + _mm_free(ptr); +#elif defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// aligned_large_pages_alloc() will return suitably aligned memory, +// if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { + + // Windows 64bit用専用。 + // Windows 32bit用ならこの機能は利用できない。 + + #if !defined(_WIN64) + return nullptr; + #else + + // ※ やねうら王独自拡張 + // LargePageはエンジンオプションにより無効化されているなら何もせずに返る。 + if (!Options["LargePageEnable"]) + return nullptr; + + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + + // 普通、最小のLarge Pageサイズは、2MBである。 + // Large Pageが使えるなら、ここでは 2097152 が返ってきているはず。 + + if (!largePageSize) + return nullptr; + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + // Large Pageを使うには、SeLockMemory権限が必要。 + // cf. http://awesomeprojectsxyz.blogspot.com/2017/11/windows-10-home-how-to-enable-lock.html + + auto OpenProcessToken_f = + OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) + return nullptr; + auto LookupPrivilegeValueA_f = + LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) + return nullptr; + auto AdjustTokenPrivileges_f = + AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + + if (!OpenProcessToken_f( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() + // succeeds, we still need to query GetLastError() to ensure that the privileges + // were actually obtained. + + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + } + } + + CloseHandle(hProcessToken); + + return mem; + + #endif +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + // ※ ここでは4KB単位でalignされたメモリが返ることは保証されているので + // 引数でalignを指定できる必要はない。(それを超えた大きなalignを行いたいケースがない) + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); + + // Fall back to regular, page-aligned, allocation if necessary + // 必要に応じて、通常のpage-alignedなメモリ割り当てにfall backする。 + + // ⇨ LargePage非対応の環境であれば、std::aligned_alloc()を用いて確保しておく。 + // 最低でも4KBでalignされたメモリが返るので、引数でalign sizeを指定できるようにする必要はない。 + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + else + // Large Pagesを確保した旨を出力。 + sync_cout << "info string Hash table allocation: Windows Large Pages used." << sync_endl; + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed + #else + constexpr size_t alignment = 4096; // small page size assumed + #endif + + // Round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; +} + +#endif + +bool has_large_pages() { + +#if defined(_WIN32) + + constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed + void* mem = aligned_large_pages_alloc_windows(page_size); + if (mem == nullptr) + { + return false; + } + else + { + aligned_large_pages_free(mem); + return true; + } + +#elif defined(__linux__) + + #if defined(MADV_HUGEPAGE) + return true; + #else + return false; + #endif + +#else + + return false; + +#endif +} + + +// aligned_large_pages_free() will free the previously memory allocated +// by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr. + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } + +#endif +} // namespace Stockfish diff --git a/source/memory.h b/source/memory.h new file mode 100644 index 000000000..c46774588 --- /dev/null +++ b/source/memory.h @@ -0,0 +1,203 @@ +#ifndef MEMORY_H_INCLUDED +#define MEMORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); + +// Memory aligned by page size, min alignment: 4096 bytes +// 大きなメモリの確保用のalloc/free。4096 bytes単位でalignmentされていることは保証されている。 +// ⇨ 置換表のメモリ確保のために使われる。Windowsのlarge pages allocの機能が使えるなら、それを用いる。 +void* aligned_large_pages_alloc(size_t size); +void aligned_large_pages_free(void* mem); + +// 現環境でlarge pagesが使えるか判定して返す。 +bool has_large_pages(); + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + // Explicitly needed to call the destructor + if constexpr (!std::is_trivially_destructible_v) + ptr->~T(); + + free_func(ptr); + return; +} + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter_array(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + + // Move back on the pointer to where the size is allocated + const size_t array_offset = std::max(sizeof(size_t), alignof(T)); + char* raw_memory = reinterpret_cast(ptr) - array_offset; + + if constexpr (!std::is_trivially_destructible_v) + { + const size_t size = *reinterpret_cast(raw_memory); + + // Explicitly call the destructor for each element in reverse order + for (size_t i = size; i-- > 0;) + ptr[i].~T(); + } + + free_func(raw_memory); +} + +// Allocates memory for a single object and places it there with placement new +template +inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC alloc_func, + Args&&... args) { + void* raw_memory = alloc_func(sizeof(T)); + ASSERT_ALIGNED(raw_memory, alignof(T)); + return new (raw_memory) T(std::forward(args)...); +} + +// Allocates memory for an array of unknown bound and places it there with placement new +template +inline std::enable_if_t, std::remove_extent_t*> +memory_allocator(ALLOC_FUNC alloc_func, size_t num) { + using ElementType = std::remove_extent_t; + + const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType)); + + // Save the array size in the memory location + char* raw_memory = + reinterpret_cast(alloc_func(array_offset + num * sizeof(ElementType))); + ASSERT_ALIGNED(raw_memory, alignof(T)); + + new (raw_memory) size_t(num); + + for (size_t i = 0; i < num; ++i) + new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType(); + + // Need to return the pointer at the start of the array so that + // the indexing in unique_ptr works. + return reinterpret_cast(raw_memory + array_offset); +} + +// +// +// aligned large page unique ptr +// +// + +template +struct LargePageDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, aligned_large_pages_free); } +}; + +template +struct LargePageArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, aligned_large_pages_free); } +}; + +template +using LargePagePtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_large_page for single objects +template +std::enable_if_t, LargePagePtr> make_unique_large_page(Args&&... args) { + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + T* obj = memory_allocator(aligned_large_pages_alloc, std::forward(args)...); + + return LargePagePtr(obj); +} + +// make_unique_large_page for arrays of unknown bound +template +std::enable_if_t, LargePagePtr> make_unique_large_page(size_t num) { + using ElementType = std::remove_extent_t; + + static_assert(alignof(ElementType) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + ElementType* memory = memory_allocator(aligned_large_pages_alloc, num); + + return LargePagePtr(memory); +} + +// +// +// aligned unique ptr +// +// + +template +struct AlignedDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, std_aligned_free); } +}; + +template +struct AlignedArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, std_aligned_free); } +}; + +template +using AlignedPtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_aligned for single objects +template +std::enable_if_t, AlignedPtr> make_unique_aligned(Args&&... args) { + const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); }; + T* obj = memory_allocator(func, std::forward(args)...); + + return AlignedPtr(obj); +} + +// make_unique_aligned for arrays of unknown bound +template +std::enable_if_t, AlignedPtr> make_unique_aligned(size_t num) { + using ElementType = std::remove_extent_t; + + const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); }; + ElementType* memory = memory_allocator(func, num); + + return AlignedPtr(memory); +} + + +// Get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, +// where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + + +} // namespace Stockfish + +#endif // #ifndef MEMORY_H_INCLUDED diff --git a/source/misc.cpp b/source/misc.cpp index 7d12ba9ec..e8a17edff 100644 --- a/source/misc.cpp +++ b/source/misc.cpp @@ -548,287 +548,6 @@ void prefetch([[maybe_unused]] const void* addr) { #endif -// -------------------- -// Large Page確保 -// -------------------- - -namespace { - // LargeMemoryを使っているかどうかがわかるように初回だけその旨を出力する。 - bool largeMemoryAllocFirstCall = true; -} - -/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation -/// does not guarantee the availability of aligned_alloc(). Memory allocated with -/// std_aligned_alloc() must be freed with std_aligned_free(). - -void* std_aligned_alloc(size_t alignment, size_t size) { - -#if defined(POSIXALIGNEDALLOC) - void* mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; -#elif defined(_WIN32) - return _mm_malloc(size, alignment); -#elif defined(__EMSCRIPTEN__) - return aligned_alloc(alignment, size); -#else - return std::aligned_alloc(alignment, size); -#endif -} - -void std_aligned_free(void* ptr) { - -#if defined(POSIXALIGNEDALLOC) - free(ptr); -#elif defined(_WIN32) - _mm_free(ptr); -#else - free(ptr); -#endif -} - -// Windows -#if defined(_WIN32) - -static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - - // Windows 64bit用専用。 - // Windows 32bit用ならこの機能は利用できない。 - #if !defined(_WIN64) - return nullptr; - #else - - // ※ やねうら王独自拡張 - // LargePageはエンジンオプションにより無効化されているなら何もせずに返る。 - if (!Options["LargePageEnable"]) - return nullptr; - - HANDLE hProcessToken { }; - LUID luid { }; - void* mem = nullptr; - - const size_t largePageSize = GetLargePageMinimum(); - - // 普通、最小のLarge Pageサイズは、2MBである。 - // Large Pageが使えるなら、ここでは 2097152 が返ってきているはず。 - - if (!largePageSize) - return nullptr; - - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - - // Large Pageを使うには、SeLockMemory権限が必要。 - // cf. http://awesomeprojectsxyz.blogspot.com/2017/11/windows-10-home-how-to-enable-lock.html - - auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!fun6) - return nullptr; - auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!fun7) - return nullptr; - auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!fun8) - return nullptr; - - // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!fun6( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; - - if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) - nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp { }; - TOKEN_PRIVILEGES prevTp { }; - DWORD prevTpLen = 0; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (fun8( // AdjustTokenPrivileges() - hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && - GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc( - nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); - - // Privilege no longer needed, restore previous state - fun8( // AdjustTokenPrivileges () - hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } - - CloseHandle(hProcessToken); - - return mem; - - #endif // _WIN64 -} - -void* aligned_large_pages_alloc(size_t allocSize) { - - // ※ ここでは4KB単位でalignされたメモリが返ることは保証されているので - // 引数でalignを指定できる必要はない。(それを超えた大きなalignを行いたいケースがない) - - //static bool firstCall = true; - - // try to allocate large pages - void* ptr = aligned_large_pages_alloc_windows(allocSize); - - // Suppress info strings on the first call. The first call occurs before 'uci' - // is received and in that case this output confuses some GUIs. - - // uciが送られてくる前に"info string"で余計な文字を出力するとGUI側が誤動作する可能性があるので - // 初回は出力を抑制するコードが入っているが、やねうら王ではisreadyでメモリ初期化を行うので - // これは気にしなくて良い。 - - // 逆に、評価関数用のメモリもこれで確保するので、何度もこのメッセージが表示されると - // 煩わしいので、このメッセージは初回のみの出力と変更する。 - -// if (!firstCall) - if (largeMemoryAllocFirstCall) - { - if (ptr) - sync_cout << "info string Hash table allocation: Windows Large Pages used." << sync_endl; - else - sync_cout << "info string Hash table allocation: Windows Large Pages not used." << sync_endl; - - largeMemoryAllocFirstCall = false; - } - - // fall back to regular, page aligned, allocation if necessary - // 4KB単位であることは保証されているはず.. - if (!ptr) - ptr = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - - // VirtualAlloc()はpage size(4KB)でalignされていること自体は保証されているはず。 - - //cout << (u64)mem << "," << allocSize << endl; - - return ptr; -} - -#else -// LargePage非対応の環境であれば、std::aligned_alloc()を用いて確保しておく。 -// 最低でも4KBでalignされたメモリが返るので、引数でalignを指定できるようにする必要はない。 - -void* aligned_large_pages_alloc(size_t allocSize) { - -#if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size -#else - constexpr size_t alignment = 4096; // assumed small page size -#endif - - // Round up to multiples of alignment - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; - void* mem = std_aligned_alloc(alignment, size); -#if defined(MADV_HUGEPAGE) - madvise(mem, size, MADV_HUGEPAGE); -#endif - - return mem; -} - -#endif - -/// aligned_large_pages_free() will free the previously allocated ttmem - -#if defined(_WIN32) - -void aligned_large_pages_free(void* mem) { - - if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) - { - DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" - << std::hex << err - << std::dec << std::endl; - exit(EXIT_FAILURE); - } -} - -#else - -void aligned_large_pages_free(void* mem) { - std_aligned_free(mem); -} - -#endif - -// -------------------- -// LargeMemory class -// -------------------- - -// メモリを確保する。Large Pageに確保できるなら、そこにする。 -// aligned_ttmem_alloc()を内部的に呼び出すので、アドレスは少なくとも2MBでalignされていることは保証されるが、 -// 気になる人のためにalignmentを明示的に指定できるようになっている。 -// メモリ確保に失敗するか、引数のalignで指定したalignmentになっていなければ、 -// エラーメッセージを出力してプログラムを終了させる。 -void* LargeMemory::alloc(size_t size, size_t align , bool zero_clear) -{ - free(); - return static_alloc(size, align, zero_clear); -} - -// alloc()で確保したメモリを開放する。 -// このクラスのデストラクタからも自動でこの関数が呼び出されるので明示的に呼び出す必要はない(かも) -void LargeMemory::free() -{ - static_free(ptr); - ptr = nullptr; -} - -// alloc()のstatic関数版。memには、static_free()に渡すべきポインタが得られる。 -void* LargeMemory::static_alloc(size_t size, size_t align, bool zero_clear) -{ - void* mem = aligned_large_pages_alloc(size); - - auto error_exit = [&](std::string mes) { - sync_cout << "info string Error! : " << mes << " in LargeMemory::alloc(" << size << "," << align << ")" << sync_endl; - Tools::exit(); - }; - - // メモリが正常に確保されていることを保証する - if (mem == nullptr) - error_exit("can't alloc enough memory."); - - // ptrがalignmentされていることを保証する - if ((reinterpret_cast(mem) % align) != 0) - error_exit("can't alloc aligned memory."); - - // ゼロクリアが必要なのか? - if (zero_clear) - { - // 確保したのが256MB以上なら並列化してゼロクリアする。 - if (size < 256 * 1024 * 1024) - // そんなに大きな領域ではないから、普通にmemset()でやっとく。 - std::memset(mem, 0, size); - else - // 並列版ゼロクリア - Tools::memclear(nullptr, mem, size); - } - - return mem; -} - -// static_alloc()で確保したメモリを開放する。 -void LargeMemory::static_free(void* mem) -{ - aligned_large_pages_free(mem); -} - - - // -------------------- // 全プロセッサを使う // -------------------- diff --git a/source/misc.h b/source/misc.h index f2766ba29..7c4699c4f 100644 --- a/source/misc.h +++ b/source/misc.h @@ -43,51 +43,6 @@ void prefetch(const void* addr); // cin/coutへの入出力をファイルにリダイレクトを開始/終了する。 void start_logger(const std::string& fname); -// -------------------- -// Large Page確保 -// -------------------- - -// Large Pageを確保するwrapper class。 -// WindowsのLarge Pageを確保する。 -// Large Pageを用いるとメモリアクセスが速くなるらしい。 -// 置換表用のメモリなどはこれで確保する。 -// cf. やねうら王、Large Page対応で10数%速くなった件 : http://yaneuraou.yaneu.com/2020/05/31/%e3%82%84%e3%81%ad%e3%81%86%e3%82%89%e7%8e%8b%e3%80%81large-page%e5%af%be%e5%bf%9c%e3%81%a710%e6%95%b0%e9%80%9f%e3%81%8f%e3%81%aa%e3%81%a3%e3%81%9f%e4%bb%b6/ -// -// Stockfishでは、Large Pageの確保~開放のためにaligned_ttmem_alloc(),aligned_ttmem_free()という関数が実装されている。 -// コードの簡単化のために、やねうら王では独自に本classからそれらを用いる。 -struct LargeMemory -{ - // LargePage上のメモリを確保する。Large Pageに確保できるなら、そこにする。 - // aligned_ttmem_alloc()を内部的に呼び出すので、アドレスは少なくとも2MBでalignされていることは保証されるが、 - // 気になる人のためにalignmentを明示的に指定できるようになっている。 - // メモリ確保に失敗するか、引数のalignで指定したalignmentになっていなければ、 - // エラーメッセージを出力してプログラムを終了させる。 - // size : 確保するサイズ [byte] - // align : 返されるメモリが守るべきalignment - // zero_clear : trueならゼロクリアされたメモリ領域を返す。 - void* alloc(size_t size, size_t align = 256 , bool zero_clear = false); - - // alloc()で確保したメモリを開放する。 - // このクラスのデストラクタからも自動でこの関数が呼び出されるので明示的に呼び出す必要はない(かも) - void free(); - - // alloc()が呼び出されてメモリが確保されている状態か? - bool alloced() const { return ptr != nullptr; } - - // alloc()のstatic関数版。この関数で確保したメモリはstatic_free()で開放する。 - static void* static_alloc(size_t size, size_t align = 256, bool zero_clear = false); - - // static_alloc()で確保したメモリを開放する。 - static void static_free(void* mem); - - ~LargeMemory() { free(); } - -private: - // allocで確保されたメモリの先頭アドレス - // (free()で開放するときにこのアドレスを用いる) - void* ptr = nullptr; -}; - // -------------------- // 統計情報 // -------------------- diff --git a/source/tt.cpp b/source/tt.cpp index ea9d07cc1..04ab79fa6 100644 --- a/source/tt.cpp +++ b/source/tt.cpp @@ -7,12 +7,15 @@ //#include //#include +#include "memory.h" #include "misc.h" #include "thread.h" // やねうら王独自拡張 #include "extra/key128.h" +using namespace Stockfish; + TranspositionTable TT; // 置換表をglobalに確保。 // 置換表のエントリーに対して与えられたデータを保存する。上書き動作 @@ -132,6 +135,8 @@ void TranspositionTable::resize(size_t mbSize) { if (newClusterCount == clusterCount) return; + aligned_large_pages_free(table); + clusterCount = newClusterCount; // tableはCacheLineSizeでalignされたメモリに配置したいので、CacheLineSize-1だけ余分に確保する。 @@ -140,7 +145,7 @@ void TranspositionTable::resize(size_t mbSize) { // cf. Explicitly zero TT upon resize. : https://github.com/official-stockfish/Stockfish/commit/2ba47416cbdd5db2c7c79257072cd8675b61721f // Large Pageを確保する。ランダムメモリアクセスが5%程度速くなる。 - table = static_cast(tt_memory.alloc(clusterCount * sizeof(Cluster), sizeof(Cluster) )); + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); // clear(); @@ -160,6 +165,7 @@ void TranspositionTable::clear() // MateEngineではこの置換表は用いないのでクリアもしない。 return; #endif + generation8 = 0; auto size = clusterCount * sizeof(Cluster); diff --git a/source/tt.h b/source/tt.h index 41c37139b..d7f5213e8 100644 --- a/source/tt.h +++ b/source/tt.h @@ -242,9 +242,6 @@ struct TranspositionTable { // --- やねうら王独自拡張 - // 置換表テーブルのメモリ確保用のhelpper - LargeMemory tt_memory; - // probe()の内部実装用。 // key_for_index : first_entry()で使うためのkey // key_for_ttentry : TTEntryに格納するためのkey diff --git a/source/types.h b/source/types.h index 676f0d19d..4664780a1 100644 --- a/source/types.h +++ b/source/types.h @@ -20,6 +20,7 @@ #include // std::string使うので仕方ない #include // std::max()を使うので仕方ない #include // std::numeric_limitsを使うので仕方ない +#include #if defined(_MSC_VER) // Disable some silly and noisy warnings from MSVC compiler