From ecf72a7526d09e88b60b6da7109fb77abb6c97c9 Mon Sep 17 00:00:00 2001 From: Avril Date: Sun, 8 Jan 2023 02:50:10 +0000 Subject: [PATCH] Improved PGO profiling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reworked panic structuring (now has overloadable handler.) Started work on out-of-place shuffler. Started work on progress-bar. Fortune for shuffle3's current commit: Great curse − 大凶 --- Makefile | 26 +++++--- include/map.h | 144 +++++++++++++++++++++++++++++++++++++++---- include/panic.h | 2 +- include/perc.h | 77 +++++++++++++++++++++++ include/shuffle.hpp | 4 +- src/map.c | 99 ++++++++++++++++++++++++++--- src/map.cpp | 31 ++++++++++ src/map_callback.cpp | 8 ++- src/panic.c | 29 ++++++++- src/panic.cpp | 47 ++++++++++++++ src/work.cpp | 8 ++- 11 files changed, 438 insertions(+), 37 deletions(-) create mode 100644 include/perc.h create mode 100644 src/map.cpp create mode 100644 src/panic.cpp diff --git a/Makefile b/Makefile index b9d1d9d..47983ea 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ CXX_OPT_FLAGS?= $(OPT_FLAGS) -felide-constructors CFLAGS += $(COMMON_FLAGS) --std=gnu11 CXXFLAGS += $(COMMON_FLAGS) --std=gnu++23 -fno-exceptions +# XXX: We might need exceptions soon, for OOP usage, because we try multiple approaches from most efficient to least. LDFLAGS += STRIP=strip @@ -50,6 +51,8 @@ PGO_OBJ_CXX = $(addprefix obj/pgo/cxx/,$(SRC_CXX:.cpp=.o)) PGO_OBJ = $(PGO_OBJ_C) $(PGO_OBJ_CXX) PGO_ITERATIONS=5 +PGO_BLOCKS={1..4} +PGO_SIZE={1024..4096} PGO_SET_LOC?=/tmp/$(PROJECT)-pgo PGO_FLAGS = -fprofile-generate @@ -112,18 +115,21 @@ pgo-reset: pgo-profile: | pgo-reset pgo-generate mkdir -p $(PGO_SET_LOC) for i in {1..$(PGO_ITERATIONS)}; do \ - dd if=/dev/urandom of=$(PGO_SET_LOC)/small bs=1024 count=1 >> /dev/null 2>&1; \ - printf "Iteration $$i / $(PGO_ITERATIONS)\r"; \ + block=$$(rng --of $(PGO_SIZE)); \ + block_count=$$(rng --of $(PGO_BLOCKS)); \ + dd if=/dev/urandom of=$(PGO_SET_LOC)/small bs=$$block count=$$block_count >> /dev/null 2>&1; \ + printf "Iteration $$i / $(PGO_ITERATIONS) ($$block * $$block_count)\r"; \ ( echo ">> $$i" >&2; \ echo ">> $$i"; \ - ./pgo-generate -s $(PGO_SET_LOC)/small; \ - ./pgo-generate -u $(PGO_SET_LOC)/small; \ - ./pgo-generate -h; \ - FCNT=1 ./test.sh ./pgo-generate; \ - FCNT=2 ./test.sh ./pgo-generate; \ - FCNT=3 ./test.sh ./pgo-generate; \ - FCNT=4 ./test.sh ./pgo-generate; \ - ) >>$(PGO_SET_LOC)/stdout.log 2>>$(PGO_SET_LOC)/stderr.log; \ + ./pgo-generate -s $(PGO_SET_LOC)/small && \ + ./pgo-generate -u $(PGO_SET_LOC)/small && \ + ./pgo-generate -h && \ + FCNT=1 ./test.sh ./pgo-generate && \ + FCNT=2 ./test.sh ./pgo-generate && \ + FCNT=3 ./test.sh ./pgo-generate && \ + FCNT=4 ./test.sh ./pgo-generate && \ + : \ + ) >>$(PGO_SET_LOC)/stdout.log 2>>$(PGO_SET_LOC)/stderr.log || \exit $$?; \ done $(shell command -v bat >/dev/null && echo "bat --pager=none" || echo cat) $(PGO_SET_LOC)/stdout.log; \ $(shell command -v bat >/dev/null && echo "bat --pager=none" || echo cat) $(PGO_SET_LOC)/stderr.log >&2 diff --git a/include/map.h b/include/map.h index e65ade3..847b455 100644 --- a/include/map.h +++ b/include/map.h @@ -2,8 +2,14 @@ #define _MAP_H #ifdef __cplusplus +#include extern "C" { #define restrict __restrict__ +#define MIFCXX(y, n) y +#define MIFC(y, n) n +#else +#define MIFCXX(y, n) n +#define MIFC(y, n) y #endif #include @@ -15,12 +21,22 @@ typedef struct mmap { size_t len; } mmap_t; +enum map_flags { + MMF_SHARED, + MMF_PRIVATE, + MMF_READONLY, +}; + +int map_raw_fd(int fd, mmap_t* restrict ptr, const size_t MIFCXX(* sz, sz[static 1])); int open_and_alloc(const char* file, mmap_t* restrict ptr, size_t sz); int open_and_map(const char* file, mmap_t* restrict ptr); int unmap_and_close(mmap_t map); +int dup_map(const mmap_t *in, mmap_t* restrict out, const size_t *new_size, enum map_flags flags) __attribute__((nonnull(1))); +int map_advise_rand(mmap_t* restrict ptr, int need); typedef void* (*map_cb)(mmap_t map, void* user); void* map_and_then(const char* file, map_cb callback, void* user); +void* map_fd_and_then(int fd, map_cb callback, void* user); #ifdef __cplusplus } @@ -28,21 +44,48 @@ void* map_and_then(const char* file, map_cb callback, void* user); #include "reinterpret.h" #include namespace mm { + enum class Access { + Random, + }; + struct mmap; + template + concept MemMap = std::derived_from + and std::is_constructible_v; struct mmap { - inline static mmap allocate(const char* file, std::size_t sz) + template + inline static M allocate(const char* file, std::size_t sz) { mmap_t map; if(!open_and_alloc(file, &map, sz)) panic("Failed to allocmap file"); - return mmap(map); + return M{map}; } inline static mmap_t create_raw(const char* file) { mmap_t map; - if (!open_and_map(file, &map)) panic("Failed to map file"); + if (!::open_and_map(file, &map)) panic("Failed to map file"); return map; } + inline static mmap_t create_raw_fd(int fd, size_t sz) + { + mmap_t map; + if (!::map_raw_fd(fd, &map, &sz)) panic("Failed to allocmap fd"); + return map; + } + inline static mmap_t create_raw_fd(int fd) + { + mmap_t map; + if (!::map_raw_fd(fd, &map, nullptr)) panic("Failed to map fd"); + return map; + } + template + inline static M map_raw_fd(int fd, std::size_t sz = 0) + { + if(sz) + return M{fd, sz}; + else return M{fd}; + } - inline explicit mmap(mmap_t raw) :inner(raw){} + inline explicit mmap(mmap_t raw) noexcept :inner(raw){} inline mmap(const char* file) : inner(create_raw(file)) {} @@ -53,29 +96,106 @@ namespace mm { } inline mmap(const mmap& copt) = delete; - inline ~mmap() + inline virtual ~mmap() { if (inner.ptr) { ::unmap_and_close(inner); } } - inline const span as_span() const { return span(as_ptr(), size()); } - inline span as_span() { return span(as_ptr(), size()); } + inline virtual mmap&& access(Access a, bool need=false) && noexcept + { + if(inner.ptr) + static_cast(*this).access(a, need); + return std::move(*this); + } + inline virtual mmap& access(Access a, bool need=false) & noexcept + { + switch(a) { + case Access::Random: ::map_advise_rand(const_cast(&inner), int(need)); + default: break; + } + return *this; + } + + inline const span as_span() const noexcept { return span(as_ptr(), size()); } + inline span as_span() noexcept { return span(as_ptr(), size()); } - inline const std::uint8_t* as_ptr() const { return (const std::uint8_t*)inner.ptr; } - inline std::uint8_t* as_ptr() { return (std::uint8_t*)inner.ptr; } + inline virtual const std::uint8_t* as_ptr() const noexcept { return static_cast(inner.ptr); } + inline virtual std::uint8_t* as_ptr() noexcept { return static_cast(inner.ptr); } - inline std::size_t size() const { return inner.len; } + inline virtual std::size_t size() const noexcept { return inner.len; } - inline int as_fd() const { return inner.fd; } - inline const mmap_t& as_raw() const { return inner; } + inline virtual int as_fd() const { return inner.fd; } + inline virtual const mmap_t& as_raw() const noexcept { return inner; } + protected: + inline mmap(int fd, size_t sz) + : inner(create_raw_fd(fd, sz)) {} + inline explicit mmap(int fd) + : inner(create_raw_fd(fd)) {} + inline virtual mmap_t& as_raw() noexcept { return const_cast( inner ); } private: const mmap_t inner; }; + //template mmap::map_raw_fd(int, size_t); // Probably not needed? + //template mmap::allocate(int, size_t); + + struct vmap : public mmap { + inline explicit vmap(mmap_t m) noexcept : mmap(m) {} + + vmap(); + vmap(size_t); + inline explicit vmap(int fd) noexcept : mmap(fd) {} + //vmap(const char* file); + + //int as_fd() const override; unneeded + + inline virtual ~vmap() { /* unmap_and_close() is called by super */ } + //TODO: Implement this ^ with `memfd_create()`, etc. + }; + + + template + inline D dup_map(const M& map, size_t resize, map_flags flags =0) + { + const mmap& m = static_cast(map); + mmap_t out; + if(! ::dup_map(&m.as_raw(), &out, &resize, flags)) panic("Failed to duplicate mapping"); + return D{out}; + } + template + inline D dup_map(const M& map, map_flags flags =0) + { + const mmap& m = static_cast(map); + mmap_t out; + if(! ::dup_map(&m.as_raw(), &out, nullptr, flags)) panic("Failed to duplicate mapping"); + return D{out}; + } +#if 0 + // dup_map() deduction guids (XXX: Are these needed, or even valid?) + template + dup_map(const mmap& m, size_t s, map_flags f) -> dup_map; + + template<> + dup_map(const mmap& m, size_t s, map_flags f) -> dup_map; + + template + dup_map(const M& m, size_t s, map_flags f) -> dup_map; + + template + dup_map(const mmap& m, map_flags f) -> dup_map; + + template<> + dup_map(const mmap& m, map_flags f) -> dup_map; + + template + dup_map(const M& m, map_flags f) -> dup_map; +#endif } #undef restrict +#undef MIFCXX +#undef MIFC #endif #endif /* _MAP_H */ diff --git a/include/panic.h b/include/panic.h index 3ce71d5..d29ab6c 100644 --- a/include/panic.h +++ b/include/panic.h @@ -20,7 +20,7 @@ extern "C++" { __attribute__((noreturn)) inline void _real_panic(const char* file, const char* function, int line, const char* fmt, Args&&... args) { panicinfo i = { file, function, line }; - _do_panic(i, fmt, std::forward(args)...); + _do_panic(i, fmt, std::move(args)...); } #define panic(fmt, ...) _real_panic(__FILE__, __func__, __LINE__, fmt __VA_OPT__(,) __VA_ARGS__) } diff --git a/include/perc.h b/include/perc.h new file mode 100644 index 0000000..f12b739 --- /dev/null +++ b/include/perc.h @@ -0,0 +1,77 @@ +#pragma once + +#ifndef __cplusplus +#error "C++ header only" +#endif + +namespace pr { + + namespace details [[gnu::visibility("inernal")]]{ + template + T* take(T*& ptr) noexcept { return std::exchange(ptr, nullptr); } + } + + struct Bar { + Bar() noexcept = default; + Bar(const Bar&) noexcept = default; + Bar(Bar&&) noexcept = default; + Bar& operator=(const Bar&) noexcept = default; + Bar& operator=(Bar&&) noexcept = default; + virtual ~Bar() = default; + + virtual void spin(int) =0; + }; + + enum bar_kind { + BRK_UNTRACKED, + BRK_TRACKED, + }; + + template + struct progress; + + template<> + struct progress + : public virtual Bar { + + progress(FILE* to); + progress(progress const&); + inline progress(progress&& mv) noexcept + : Bar() + , _perc(mv._perc) + , _output(details::take(mv._output)) {} + + virtual ~progress(); + + void spin(int) override; + + //TODO: this + protected: + union { + double _perc; + size_t _num; // not used here + }; + FILE* _output; + }; + + template<> + class progress + : public progress { + using base_t = progress; + public: + inline progress(FILE* to, size_t max) : base_t(to), _num(0), _max(max) {} + progress(const progress&) = default; + progress(progress&& m) : base_t(std::move(m)), _num(std::exchange(m._num, 0)), _max(m._max) {} + + void spin(int) override; + //TODO: this + + virtual ~progress() {} + + protected: + using base_t::_output; + private: + using base_t::_num; + size_t _max; + }; +} diff --git a/include/shuffle.hpp b/include/shuffle.hpp index 4686ed5..0262a91 100644 --- a/include/shuffle.hpp +++ b/include/shuffle.hpp @@ -14,7 +14,7 @@ namespace rng { inline void shuffle(R& rng, span span) { if(!span.size()) return; - std::cout << " -> shuffling " << span.size() << " objects..."; + std::cout << " -> shuffling " << span.size() << " objects..." << std::flush; for(std::size_t i=span.size()-1;i>0;i--) { auto j = rng.next_long(i); @@ -63,7 +63,7 @@ namespace rng { #undef MAP #undef DYN - std::cout << " -> unshuffling " << span.size() << " objects..."; + std::cout << " -> unshuffling " << span.size() << " objects..." << std::flush; for(std::size_t i=span.size()-1;i>0;i--) rng_values.push_back(rng.next_long(i)); diff --git a/src/map.c b/src/map.c index 60e5011..f74f004 100644 --- a/src/map.c +++ b/src/map.c @@ -12,6 +12,51 @@ #include +__attribute__((pure, nonnull(1))) +static inline int _map(mmap_t* restrict map) +{ + //TODO: Based on map->len, choose (or calculate) appropriate `MAP_HUGE_*` flag + return (map->ptr = mmap(NULL, map->len, PROT_READ | PROT_WRITE, MAP_SHARED, map->fd, 0)) != MAP_FAILED; +} +//#define _map(...) _map(__VA_ARGS__, 0) + +int map_advise_rand(mmap_t* restrict ptr, int need) +{ + int flags = MADV_RANDOM | (need ? MADV_WILLNEED : 0); + if(madvise(ptr->ptr, ptr->len, flags)) { + perror("madvise() failed"); + return 0; + } + return 1; +} + +int map_raw_fd(int fd, mmap_t* restrict ptr, const size_t sz[static 1]) +{ + if(fd < 0) return 0; + struct stat st; + + + if(sz && ftruncate(fd, *sz)) { + perror("Failed to allocate"); + return 0; + } else if(fstat(fd, &st) < 0) { + perror("Failed to stat file"); + return 0; + } + + struct mmap map = { .fd = fd, .ptr = NULL, .len = sz ? *sz : st.st_size }; + + if (!_map(&map)) { + perror("mmap() failed"); + return 0; + } + + *ptr = map; + + return 1; + +} + int open_and_map(const char* file, mmap_t* restrict ptr) { int fd; @@ -27,9 +72,9 @@ int open_and_map(const char* file, mmap_t* restrict ptr) return 0; } - register struct mmap map = { .fd = fd, .ptr = NULL, .len = st.st_size }; + struct mmap map = { .fd = fd, .ptr = NULL, .len = st.st_size }; - if ((map.ptr = mmap(NULL, map.len, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0)) == MAP_FAILED) { + if (!_map(&map)) { perror("mmap() failed"); close(fd); return 0; @@ -48,16 +93,16 @@ int open_and_alloc(const char* file, mmap_t* restrict ptr, size_t sz) return 0; } - if(fallocate(fd, 0, 0, sz)) + if(ftruncate(fd, sz)) { perror("Failed to allocate"); close(fd); return 0; } - register struct mmap map = { .fd = fd, .ptr = NULL, .len = sz }; + struct mmap map = { .fd = fd, .ptr = NULL, .len = sz }; - if ((map.ptr = mmap(NULL, map.len, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0)) == MAP_FAILED) { + if (!_map(&map)) { perror("mmap() failed"); close(fd); return 0; @@ -68,10 +113,12 @@ int open_and_alloc(const char* file, mmap_t* restrict ptr, size_t sz) return 1; } - -int unmap_and_close(mmap_t map) +inline +int unmap_and_close_s(mmap_t map, int flags) { register int rval=1; + if (map.fd && msync(map.ptr, map.len, flags)) + perror("msync() failed"); if (munmap(map.ptr, map.len) < 0) { perror("munmap() failed"); rval=0; @@ -83,3 +130,41 @@ int unmap_and_close(mmap_t map) return rval; } + + +int unmap_and_close(mmap_t map) +{ + return unmap_and_close_s(map, MS_SYNC /* | MS_INVALIDATE XXX: I don't think this is needed, we son't be using the mapping anymore*/); +} + +__attribute__((nonnull(1))) +int dup_map(const mmap_t *in, mmap_t* restrict ptr, const size_t *new_size, enum map_flags flags) +{ + int fd; + if ( (fd = dup(in->fd)) < 0 ) + { + perror("dup() failed"); + return 0; + } + + size_t sz; + if(new_size) { + if(ftruncate(fd, sz = *new_size)) + { + perror("Failed to set cloned map size"); + close(fd); + return 0; + } + } else sz = in->len; + + struct mmap map = { .fd = fd, .ptr = NULL, .len = sz }; + + if ((map.ptr = mmap(NULL, map.len, PROT_READ | (flags & MMF_READONLY ? 0 : PROT_WRITE), (flags & MMF_PRIVATE) ? MAP_PRIVATE : MAP_SHARED,fd, 0)) == MAP_FAILED) { + perror("mmap() failed"); + close(fd); + return 0; + } + + *ptr = map; + return 1; +} diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 0000000..701af4e --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,31 @@ + +#include +#include +#include +#include +#include + +#include +#include + + + +namespace { + constexpr inline auto MEMFD_DEFAULT_FLAGS = MFD_CLOEXEC; + int memfd_alloc(const char* name, size_t size = 0, int flags = MEMFD_DEFAULT_FLAGS) + { + int fd = memfd_create(name, flags); + if(fd < 0) panic("memfd_create() failed"); + if(size) + if(ftruncate(fd, size)) { close(fd); panic("ftruncate() failed"); } + return fd; + } +} + +namespace mm { + vmap::vmap() + : mmap(create_raw_fd(memfd_alloc(__PRETTY_FUNCTION__))) {} + vmap::vmap(size_t sz) + : mmap(create_raw_fd(memfd_alloc(__PRETTY_FUNCTION__, sz))) {} + +} diff --git a/src/map_callback.cpp b/src/map_callback.cpp index 3c3f7f5..81359f1 100644 --- a/src/map_callback.cpp +++ b/src/map_callback.cpp @@ -7,5 +7,11 @@ extern "C" void* map_and_then(const char* file, map_cb callback, void* user) { mm::mmap map(file); - return callback(map.as_raw(), user); + return callback(std::as_const(map).as_raw(), user); +} + +extern "C" void* map_fd_and_then(int fd, map_cb callback, void* user) +{ + auto map = mm::mmap::map_raw_fd(fd); + return callback(std::as_const(map).as_raw(), user); } diff --git a/src/panic.c b/src/panic.c index 20a10b1..21d96e8 100644 --- a/src/panic.c +++ b/src/panic.c @@ -1,18 +1,41 @@ #include #include +#include #include #include #include -__attribute__((noreturn)) void _do_panic(struct panicinfo info, const char* fmt, ...) +#ifndef DEFAULT_PANIC_HANDLER +#if __cpp_exceptions || 1 /* XXX: Cannot detect this here */ +# define DEFAULT_PANIC_HANDLER "_panic__start_unwind_cxx" +#else +# define DEFAULT_PANIC_HANDLER "_panic__start_unwind_abort" +#endif +#endif + + +extern void _panic__start_unwind_cxx(void); +static void _panic__start_unwind_abort(void*) +__attribute__((weakref("abort"), noreturn)); + +static void _panic__start_unwind(void* payload) +__attribute__((noreturn, weakref(DEFAULT_PANIC_HANDLER))); + +__attribute__((noreturn, weak)) void _do_panic(struct panicinfo info, const char* fmt, ...) { va_list li; va_start(li, fmt); - fprintf(stderr, BOLD(UNDL(FRED("[!]"))) " (" BOLD("%s") "->" BOLD(FRED("%s")) ":" FYEL("%d") ") " BOLD(FRED("fatal error")) ": ", info.file, info.function, info.line); +#define FMT_TTY BOLD(UNDL(FRED("[!]"))) " (" BOLD("%s") "->" BOLD(FRED("%s")) ":" FYEL("%d") ") " BOLD(FRED("fatal error")) ": " +#define FMT_FIL "[!]" " (" "%s" "->" "%s" ":" "%d" ") " "fatal error" ": " + fprintf(stderr, isatty(fileno(stderr)) + ? FMT_TTY + : FMT_FIL , info.file, info.function, info.line); vfprintf(stderr, fmt, li); fprintf(stderr, "\n"); va_end(li); - abort(); + + _panic__start_unwind(&info); + __builtin_unreachable(); } diff --git a/src/panic.cpp b/src/panic.cpp new file mode 100644 index 0000000..f650337 --- /dev/null +++ b/src/panic.cpp @@ -0,0 +1,47 @@ + +#include +#include + +#include + +extern "C" { + struct ErrorPayload {}; +} + +struct FatalError { + FatalError(void* payload) + : payload(payload) {} + FatalError(FatalError&& m) + : payload(std::exchange(m.payload, nullptr)) {} + + FatalError& operator=(FatalError&& m) + { + if(this != &m) { + if(payload) destroy(); + payload = std::exchange(m.payload, nullptr); + } return *this; + } + virtual ~FatalError() noexcept { + destroy(); + } +protected: + virtual void destroy() noexcept + { + /*if(auto* p = dynamic_cast(payload)) + delete p;*/ + } +public: + void* payload; +}; + +extern "C" { + [[noreturn, gnu::weak]] + void _panic__start_unwind_cxx(void* payload) + { +#if __EXCEPTIONS + throw FatalError(payload); +#else + ::abort(); +#endif + } +} diff --git a/src/work.cpp b/src/work.cpp index 06efa40..8c40c24 100644 --- a/src/work.cpp +++ b/src/work.cpp @@ -48,6 +48,7 @@ namespace work int xshuffle_ip(const char* file) { mm::mmap map(file); + map.access(mm::Access::Random, true); if constexpr(unshuffle) { @@ -89,10 +90,15 @@ namespace work template int xshuffle_op(const char* ifile, const char* ofile, bool is_buffered) { + //TODO: Use libcow's `cow_create_fd()`/`Cow::Cow(fd, size)` to try to map `ifile` + // Then, clone it, and use the `Fake` to do the shuffling on, and write the data to `ofile`. + // If `ifile` cannot be mapped, create a shim `Cow(size(ifile))`, `sendfile()/copy_file_range()/splice()` `ifile` into the shim memory fd, and use that (do *not* clone it to a Fake,) + // Then, perform the shuffling on the original `Cow`, and `sendfile()/copy_file_range()/splice()` the `ifile` file memory descriptor used for the `Cow` into `ofile`, and then destroy the `Cow`, **make sure to `msync()` the Cow'd range before the copy**. + mm::vmap imap; //{ifile}; if constexpr(unshuffle) { - + } else { }