Skip to content

Commit d13c725

Browse files
authored
[Linux] Implement KSM Kernel Samepage Merging with Maps (#4601)
* KSM work * Windows fixes * Add KSM logging, cleanup * Cleanup raycast logging
1 parent 25826c6 commit d13c725

File tree

9 files changed

+357
-33
lines changed

9 files changed

+357
-33
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ compile_flags.txt
6868

6969
# CMake Files
7070
cmake-build-relwithdebinfo/*
71+
skill-caps.diff

common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ SET(common_sources
9898
json/json.hpp
9999
json/jsoncpp.cpp
100100
zone_store.cpp
101+
memory/ksm.hpp
101102
net/console_server.cpp
102103
net/console_server_connection.cpp
103104
net/crc32.cpp

common/eqemu_logsys.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ namespace Logs {
144144
XTargets,
145145
EvolveItem,
146146
PositionUpdate,
147+
KSM,
147148
MaxCategoryID /* Don't Remove this */
148149
};
149150

@@ -246,7 +247,8 @@ namespace Logs {
246247
"Corpses",
247248
"XTargets",
248249
"EvolveItem",
249-
"PositionUpdate"
250+
"PositionUpdate",
251+
"KSM" // Kernel Samepage Merging
250252
};
251253
}
252254

common/eqemu_logsys_log_aliases.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,17 @@
861861

862862
#define LogPositionUpdateDetail(message, ...) do {\
863863
if (LogSys.IsLogEnabled(Logs::Detail, Logs::PositionUpdate))\
864-
OutF(LogSys, Logs::Detail, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
864+
OutF(LogSys, Logs::Detail, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__); \
865+
} while (0)
866+
867+
#define LogKSM(message, ...) do {\
868+
if (LogSys.IsLogEnabled(Logs::General, Logs::KSM))\
869+
OutF(LogSys, Logs::General, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
870+
} while (0)
871+
872+
#define LogKSMDetail(message, ...) do {\
873+
if (LogSys.IsLogEnabled(Logs::Detail, Logs::KSM))\
874+
OutF(LogSys, Logs::Detail, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
865875
} while (0)
866876

867877
#define Log(debug_level, log_category, message, ...) do {\

common/memory/ksm.hpp

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#ifndef EQEMU_KSM_HPP
2+
#define EQEMU_KSM_HPP
3+
4+
#include "../eqemu_logsys.h"
5+
#include <iostream>
6+
#include <vector>
7+
#include <cstring>
8+
#ifdef _WIN32
9+
#include <malloc.h> // For _aligned_malloc, _aligned_free
10+
#include <windows.h>
11+
#else
12+
#include <sys/mman.h> // For madvise
13+
#include <unistd.h> // For sysconf, sbrk
14+
#endif
15+
16+
17+
// Page-aligned allocator for std::vector
18+
template <typename T>
19+
class PageAlignedAllocator {
20+
public:
21+
using value_type = T;
22+
23+
PageAlignedAllocator() noexcept = default;
24+
template <typename U>
25+
PageAlignedAllocator(const PageAlignedAllocator<U>&) noexcept {}
26+
27+
T* allocate(std::size_t n) {
28+
void* ptr = nullptr;
29+
size_t size = n * sizeof(T);
30+
31+
#ifdef _WIN32
32+
// Simply allocate memory without alignment
33+
ptr = malloc(size);
34+
if (!ptr) throw std::bad_alloc();
35+
#else
36+
size_t alignment = getPageSize(); // Get the system's page size
37+
if (posix_memalign(&ptr, alignment, size) != 0) {
38+
throw std::bad_alloc();
39+
}
40+
#endif
41+
return static_cast<T*>(ptr);
42+
}
43+
44+
void deallocate(T* p, std::size_t) noexcept {
45+
free(p);
46+
}
47+
48+
private:
49+
size_t getPageSize() const
50+
{
51+
#ifdef _WIN32
52+
SYSTEM_INFO sysInfo;
53+
GetSystemInfo(&sysInfo);
54+
return sysInfo.dwPageSize; // Page size in bytes
55+
#else
56+
return static_cast<size_t>(sysconf(_SC_PAGESIZE));
57+
#endif
58+
};
59+
};
60+
61+
template <typename T, typename U>
62+
bool operator==(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
63+
return true;
64+
}
65+
66+
template <typename T, typename U>
67+
bool operator!=(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
68+
return false;
69+
}
70+
71+
// Kernel Samepage Merging (KSM) functionality
72+
namespace KSM {
73+
74+
#ifdef _WIN32
75+
// Windows-specific placeholder functions (no-op)
76+
inline void CheckPageAlignment(void* ptr) {
77+
}
78+
79+
inline void* AllocatePageAligned(size_t size) {
80+
return memset(malloc(size), 0, size);
81+
}
82+
83+
inline void MarkMemoryForKSM(void* start, size_t size) {
84+
}
85+
86+
inline void AlignHeapToPageBoundary() {
87+
}
88+
89+
inline void* MarkHeapStart() {
90+
return nullptr;
91+
}
92+
93+
inline size_t MeasureHeapUsage(void* start) {
94+
return 0;
95+
}
96+
#else
97+
// Linux-specific functionality
98+
inline void CheckPageAlignment(void* ptr) {
99+
size_t page_size = sysconf(_SC_PAGESIZE);
100+
if (reinterpret_cast<uintptr_t>(ptr) % page_size == 0) {
101+
LogKSMDetail("Memory is page-aligned [{}]", ptr);
102+
} else {
103+
LogKSMDetail("Memory is NOT page-aligned [{}]", ptr);
104+
}
105+
}
106+
107+
inline void* AllocatePageAligned(size_t size) {
108+
size_t page_size = sysconf(_SC_PAGESIZE);
109+
void* aligned_ptr = nullptr;
110+
if (posix_memalign(&aligned_ptr, page_size, size) != 0) {
111+
LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size);
112+
}
113+
std::memset(aligned_ptr, 0, size);
114+
return aligned_ptr;
115+
}
116+
117+
inline void MarkMemoryForKSM(void* start, size_t size) {
118+
if (madvise(start, size, MADV_MERGEABLE) == 0) {
119+
LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size);
120+
} else {
121+
perror("madvise failed");
122+
}
123+
}
124+
125+
inline void AlignHeapToPageBoundary() {
126+
size_t page_size = sysconf(_SC_PAGESIZE);
127+
if (page_size == 0) {
128+
LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size);
129+
return;
130+
}
131+
132+
void* current_break = sbrk(0);
133+
if (current_break == (void*)-1) {
134+
LogKSM("Failed to retrieve the current program break");
135+
return;
136+
}
137+
138+
uintptr_t current_address = reinterpret_cast<uintptr_t>(current_break);
139+
size_t misalignment = current_address % page_size;
140+
141+
if (misalignment != 0) {
142+
size_t adjustment = page_size - misalignment;
143+
if (sbrk(adjustment) == (void*)-1) {
144+
LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment);
145+
return;
146+
}
147+
}
148+
149+
LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0));
150+
}
151+
152+
inline void* MarkHeapStart() {
153+
void* current_pos = sbrk(0);
154+
AlignHeapToPageBoundary();
155+
return current_pos;
156+
}
157+
158+
inline size_t MeasureHeapUsage(void* start) {
159+
void* current_break = sbrk(0);
160+
return static_cast<char*>(current_break) - static_cast<char*>(start);
161+
}
162+
#endif
163+
164+
165+
inline size_t getPageSize()
166+
{
167+
#ifdef _WIN32
168+
SYSTEM_INFO sysInfo;
169+
GetSystemInfo(&sysInfo);
170+
return sysInfo.dwPageSize; // Page size in bytes
171+
#else
172+
return static_cast<size_t>(sysconf(_SC_PAGESIZE)); // POSIX page size
173+
#endif
174+
};
175+
176+
template <typename T>
177+
inline void PageAlignVectorAligned(std::vector<T, PageAlignedAllocator<T>>& vec) {
178+
if (vec.empty()) {
179+
return;
180+
}
181+
182+
size_t page_size = getPageSize();
183+
void* start = vec.data();
184+
size_t size = vec.size() * sizeof(T);
185+
186+
// Check if the memory is page-aligned
187+
if (reinterpret_cast<std::uintptr_t>(start) % page_size != 0) {
188+
// Allocate a new aligned vector
189+
std::vector<T, PageAlignedAllocator<T>> aligned_vec(vec.get_allocator());
190+
aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy
191+
192+
// Copy elements from the original vector
193+
aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end());
194+
195+
// Swap the aligned vector with the original vector
196+
vec.swap(aligned_vec);
197+
198+
// Clear the temporary aligned vector to free its memory
199+
aligned_vec.clear();
200+
201+
// Verify the new alignment
202+
start = vec.data();
203+
if (reinterpret_cast<std::uintptr_t>(start) % page_size != 0) {
204+
throw std::runtime_error("Failed to align vector memory to page boundaries.");
205+
}
206+
207+
LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size);
208+
} else {
209+
LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size);
210+
}
211+
212+
#ifndef _WIN32
213+
// Mark memory for KSM (only on non-Windows systems)
214+
MarkMemoryForKSM(start, size);
215+
#endif
216+
}
217+
218+
}
219+
220+
#endif // EQEMU_KSM_HPP

world/cli/test.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <cereal/archives/json.hpp>
22
#include <cereal/types/vector.hpp>
3+
#include <iomanip>
34
#include "../../common/events/player_events.h"
5+
#include "../../common/memory/ksm.hpp"
46

57
void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::string &description)
68
{
@@ -10,5 +12,21 @@ void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::
1012
return;
1113
}
1214

13-
15+
void* start_marker = KSM::MarkHeapStart();
16+
std::cout << "Start marker: " << start_marker << "\n";
17+
18+
std::vector<std::string> vec = {};
19+
for (int i = 0; i < 100000; i++) {
20+
vec.push_back("Some random string");
21+
}
22+
23+
// Measure allocated memory size
24+
size_t allocated_size = KSM::MeasureHeapUsage(start_marker);
25+
// Convert to MB as a float and output with precision
26+
double allocated_size_mb = static_cast<double>(allocated_size) / (1024 * 1024);
27+
std::cout << std::fixed << std::setprecision(3)
28+
<< "Allocated size: " << allocated_size_mb << " MB\n";
29+
30+
// Mark memory for KSM
31+
KSM::MarkMemoryForKSM(start_marker, allocated_size);
1432
}

zone/gm_commands/loc.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ void command_loc(Client *c, const Seperator *sep)
99

1010
auto target_position = target->GetPosition();
1111

12+
// check los benchmark
13+
BenchTimer timer;
14+
for (int i = 0; i < 1000; i++) {
15+
zone->zonemap->CheckLoS(c->GetPosition(), target_position);
16+
}
17+
c->Message(
18+
Chat::White,
19+
fmt::format(
20+
"CheckLoS benchmark took [{}]",
21+
timer.elapsed()
22+
).c_str()
23+
);
24+
1225
c->Message(
1326
Chat::White,
1427
fmt::format(

zone/map.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "raycast_mesh.h"
88
#include "zone.h"
99
#include "../common/file.h"
10+
#include "../common/memory/ksm.hpp"
1011

1112
#include <algorithm>
1213
#include <map>
@@ -953,6 +954,7 @@ bool Map::LoadV2(FILE *f) {
953954
return true;
954955
}
955956

957+
956958
void Map::RotateVertex(glm::vec3 &v, float rx, float ry, float rz) {
957959
glm::vec3 nv = v;
958960

0 commit comments

Comments
 (0)