Compare commits

...

10 commits

12 changed files with 417 additions and 283 deletions

View file

@ -194,7 +194,7 @@ void bench_window::on_click_new_round() {
.bnumber = 0
});
if (!lib94::init_round(warriors.data(), warriors.size())) {
if (!lib94::init_round(warriors.data(), warriors.size(), 0, true)) {
Gtk::MessageDialog *md = new Gtk::MessageDialog("warriors do not fit in core; removing last warrior");
md->set_transient_for(*this);
md->set_modal();
@ -208,7 +208,7 @@ void bench_window::on_click_new_round() {
update_ui();
return;
}
assert(lib94::init_round(warriors.data(), warriors.size()));
assert(lib94::init_round(warriors.data(), warriors.size(), 0, true));
}
core.mut.lock();
@ -320,7 +320,17 @@ void bench_window::on_click_remove_warrior() {
break;
}
on_click_new_round();
if (warriors.size())
on_click_new_round();
else {
lib94::remove_all_warriors();
lib94::clear_address_sets();
core.mut.lock();
core.clear_all();
core.mut.unlock();
update_ui();
}
delete w;

View file

@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include <deque>
#include <mpi.h>
#include <set>
#ifndef LIB94_CORE_SIZE
@ -22,7 +23,8 @@ namespace lib94 {
//it has to be at least big enough to represent LIB94_CORE_SIZE squared,
//since it is used as an intermediate for all of the instructions,
//including in particular MUL.
typedef int_least32_t number_t;
typedef int32_t number_t;
#define LIB94_MPI_NUMBER_T MPI_INT32_T
enum opcode : uint8_t {
DAT, MOV, ADD, SUB,
@ -63,6 +65,15 @@ namespace lib94 {
std::vector<instruction> instructions;
};
//serialize a warrior in such a way that it can be read back by deserialize_warrior.
//does not consider name and author.
void serialize_warrior(const warrior *the_warrior, std::vector<uint8_t> &into);
//deserialize a warrior that has been serialized by serialize_warrior.
//no error checking is performed.
//does not consider name and author.
warrior *deserialize_warrior(const uint8_t *from);
//this seeds the prng used to place warriors into the core at the start of a round
//if this is never called, 0 is used as a seed and the placements are deterministic.
void seed_prng(uint_fast64_t seed);
@ -91,15 +102,24 @@ namespace lib94 {
//does not effect the address sets.
void clear_core_random();
//clears the address sets, places the supplied warriors into the core, and starts one process for
//each supplied warrior at its org. the count parameter specifies the number of warriors in the
//array. each of the warriors in the array must not be deleted for the duration of the round, but
//the array itself may be deleted after this function returns. on success, this function returns
//true. on failure (i.e. when the warriors do not all fit into the core), this function returns
//false. note that this function does not clear the core before placing the warriors, so you may
//want to call clear_core or clear_core_random before calling this. note also that you probably
//want to call seed_prng, for example with the current time, before the first time you call this.
bool init_round(const warrior *const *warriors, size_t count);
//clears the address sets, places the supplied warriors into the core, and starts one
//process for each supplied warrior at its org. the count parameter specifies the number
//of warriors in the array. each of the warriors in the array must not be deleted for
//the duration of the round, but the array itself may be deleted after this function
//returns. if the offsets parameter is not null, it specifies where in the core the first
//instruction of each warrior is put. if it is null, the warriors are placed at random
//non-overlapping locations, respecting the order that they appear in the array. if
//shuffle is true, the warriors' turn order is decided randomly. if shuffle is false,
//the turn order is the order that they appear in the warriors array. on success, this
//function returns true. on failure (i.e. when offsets is null and the warriors do not
//all fit into the core), this function returns false. note that this function does not
//clear the core before placing the warriors, so you may want to call clear_core or
//clear_core_random before calling this. note also that if you are passing null for
//offsets or setting shuffle to true, you probably want to call seed_prng, for example
//with the current time, before the first time you call this.
bool init_round(const warrior *const *warriors, size_t count, const number_t *offsets, bool shuffle);
void remove_all_warriors();
//returns the number of warriors who have at least one process
size_t alive_warrior_count();
@ -138,6 +158,24 @@ namespace lib94 {
template <bool update_address_sets>
const warrior *single_step();
//convenience method - reads the contents of a file and then calls compile_warrior.
//if the file cannot be read, returns a null pointer. see comment on compile_warrior.
warrior *compile_warrior_from_file(std::string path);
//convenience method - calls clear_core(background), then init_round(warriors, count,
//offsets, shuffle), then single_step<false> until either one warrior remains or that
//has been called steps_to_tie times. if one warrior remains, returns the pointer to
//that warrior. if a tie is reached, returns a null pointer. this asserts that the call
//to init_round returns true. see comment on init_round.
const warrior *do_round(const instruction &background, const warrior *const *warriors, size_t count, const number_t *offsets, bool shuffle, long steps_to_tie);
//runs every possible offset and turn order using do_round.
//asserts that total length is not more than core size.
//should be called from everyone in a communicator.
//assumes comm ranks go from 0 to comm size - 1.
template <bool print_progress>
void tabulate_mpi(const instruction &background, const warrior *w1, const warrior *w2, long steps_to_tie, int &w1_wins_out, int &w2_wins_out, int &rounds_out, MPI_Comm communicator);
}
#endif

View file

@ -1,8 +1,10 @@
#include <lib94/lib94.hpp>
#include <functional>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <random>
#include <mpi.h>
namespace lib94 {
@ -55,57 +57,69 @@ namespace lib94 {
static std::vector<warrior_info> warrior_infos;
std::deque<warrior_info *> alive_warriors;
bool init_round(const warrior *const *warriors, size_t count) {
bool init_round(const warrior *const *warriors, size_t count, const number_t *offsets, bool shuffle) {
clear_address_sets();
warrior_infos.clear();
alive_warriors = std::deque<warrior_info *>();
std::vector<number_t> gap_sizes;
gap_sizes.resize(count);
std::vector<number_t> offsets_vector;
std::uniform_real_distribution phi_dist(0.0, 1.0);
number_t gap_remaining = LIB94_CORE_SIZE;
if (!offsets) {
offsets_vector.resize(count);
offsets_vector[0] = 0;
std::uniform_real_distribution phi_dist(0.0, 1.0);
number_t gap_remaining = LIB94_CORE_SIZE;
for (size_t i = 0; i < count; ++i) {
number_t wlength = warriors[i]->instructions.size();
if (wlength > gap_remaining)
return false;
gap_remaining -= wlength;
}
for (size_t i = 1; i < count; ++i) {
number_t gap_size = std::floor(gap_remaining * (1.0 - std::pow(phi_dist(prng), 1.0 / (count - i))));
offsets_vector[i] = offsets_vector[i - 1] + warriors[i - 1]->instructions.size() + gap_size;
gap_remaining -= gap_size;
}
offsets = offsets_vector.data();
for (size_t i = 0; i < count; ++i) {
number_t wlength = warriors[i]->instructions.size();
if (wlength > gap_remaining)
return false;
gap_remaining -= wlength;
}
for (size_t i = 0; i < count; ++i) {
number_t gap_size = std::floor(gap_remaining * (1.0 - std::pow(phi_dist(prng), 1.0 / (count - i))));
gap_sizes[i] = gap_size;
gap_remaining -= gap_size;
}
size_t place_at = 0;
for (size_t i = 0; i < count; ++i) {
const warrior *w = warriors[i];
const number_t offset = offsets[i];
for (number_t i = 0; i < (number_t)w->instructions.size(); ++i) {
assert(place_at + i < LIB94_CORE_SIZE);
core[place_at + i] = w->instructions[i];
add_written_instruction(core + place_at + i);
assert(offset + i < LIB94_CORE_SIZE);
core[offset + i] = w->instructions[i];
add_written_instruction(core + offset + i);
}
warrior_infos.push_back({});
warrior_info *wi = &warrior_infos.back();
wi->the_warrior = w;
assert(place_at + w->org < LIB94_CORE_SIZE);
wi->processes.push_back(place_at + w->org);
assert(offset + w->org < LIB94_CORE_SIZE);
wi->processes.push_back(offsets[i] + w->org);
place_at += w->instructions.size() + gap_sizes[i];
}
std::shuffle(warrior_infos.begin(), warrior_infos.end(), prng);
if (shuffle)
std::shuffle(warrior_infos.begin(), warrior_infos.end(), prng);
for (warrior_info &wi : warrior_infos)
alive_warriors.push_back(&wi);
return true;
}
void remove_all_warriors() {
alive_warriors.clear();
}
size_t alive_warrior_count() {
return alive_warriors.size();
}
@ -307,4 +321,114 @@ namespace lib94 {
template const warrior *single_step<true>();
template const warrior *single_step<false>();
const warrior *do_round(const instruction &background, const warrior *const *warriors, size_t count, const number_t *offsets, bool shuffle, long steps_to_tie) {
clear_core(background);
assert(init_round(warriors, count, offsets, shuffle));
size_t warriors_left = count;
for (long i = 0; i < steps_to_tie; ++i)
if (single_step<false>() != 0) {
--warriors_left;
if (warriors_left == 1)
return get_next_warrior();
}
return 0;
}
template <bool print_progress>
void tabulate_mpi(const instruction &background, const warrior *w1, const warrior *w2, long steps_to_tie, int &w1_wins_out, int &w2_wins_out, int &rounds_out, MPI_Comm communicator) {
assert(w1->instructions.size() + w2->instructions.size() <= LIB94_CORE_SIZE);
std::vector<number_t> w2_offsets;
for (number_t i = w1->instructions.size(); i + w2->instructions.size() <= LIB94_CORE_SIZE; ++i)
w2_offsets.push_back(i);
int comm_size;
int comm_rank;
MPI_Comm_size(communicator, &comm_size);
MPI_Comm_rank(communicator, &comm_rank);
if constexpr (print_progress)
if (comm_rank == 0) {
for (int i = 0; i < comm_size; ++i)
std::cout << '\n';
std::cout << std::flush;
}
int actual_count = w2_offsets.size();
int per_process = (actual_count - 1) / comm_size + 1;
w2_offsets.resize(per_process * comm_size);
for (int i = actual_count; i < per_process * comm_size; ++i)
w2_offsets[i] = LIB94_CORE_SIZE + 1;
std::shuffle(w2_offsets.begin(), w2_offsets.end(), prng);
number_t *recv_buffer = new number_t[per_process];
MPI_Scatter(w2_offsets.data(), per_process, LIB94_MPI_NUMBER_T, recv_buffer, per_process, LIB94_MPI_NUMBER_T, 0, communicator);
int wins[2] = {0, 0};
const warrior *w1_first_warriors[2] = {w1, w2};
number_t w1_first_offsets[2] = {0, 0};
const warrior *w2_first_warriors[2] = {w2, w1};
number_t w2_first_offsets[2] = {0, 0};
int last_percent = -1;
for (int i = 0; i < per_process; ++i)
if (recv_buffer[i] != LIB94_CORE_SIZE + 1) {
if constexpr (print_progress) {
int percent = i * 100 / per_process;
if (percent != last_percent) {
std::cout << "\x1b[" << comm_size - comm_rank << "A\x1b[3G";
std::cout << "worker " << comm_rank + 1 << ": " << percent << "%\x1b[0K";
std::cout << "\x1b[" << comm_size - comm_rank << "B\x1b[1G";
std::cout << std::flush;
}
}
w1_first_offsets[1] = recv_buffer[i];
const warrior *w = do_round(background, w1_first_warriors, 2, w1_first_offsets, false, steps_to_tie);
if (w == w1)
++wins[0];
else if (w == w2)
++wins[1];
w2_first_offsets[0] = recv_buffer[i];
w = do_round(background, w2_first_warriors, 2, w2_first_offsets, false, steps_to_tie);
if (w == w1)
++wins[0];
else if (w == w2)
++wins[1];
}
if constexpr (print_progress) {
std::cout << "\x1b[" << comm_size - comm_rank << "A\x1b[3G";
std::cout << "worker " << comm_rank + 1 << ": complete\x1b[0K";
std::cout << "\x1b[" << comm_size - comm_rank << "B\x1b[1G";
std::cout << std::flush;
}
int win_sums[2];
MPI_Allreduce(wins, win_sums, 2, MPI_INT, MPI_SUM, communicator);
w1_wins_out = win_sums[0];
w2_wins_out = win_sums[1];
rounds_out = actual_count * 2;
}
template void tabulate_mpi<true>(const instruction &background, const warrior *w1, const warrior *w2, long steps_to_tie, int &w1_wins_out, int &w2_wins_out, int &rounds_out, MPI_Comm communicator);
template void tabulate_mpi<false>(const instruction &background, const warrior *w1, const warrior *w2, long steps_to_tie, int &w1_wins_out, int &w2_wins_out, int &rounds_out, MPI_Comm communicator);
}

View file

@ -1,7 +1,9 @@
#include <lib94/lib94.hpp>
#include <functional>
#include <cassert>
#include <cstring>
#include <fstream>
#include <sstream>
#include <cctype>
#include <memory>
#include <map>
@ -46,6 +48,26 @@ namespace lib94 {
mode_chars[instr.bmode] + std::to_string(instr.bnumber);
}
void serialize_warrior(const warrior *the_warrior, std::vector<uint8_t> &into) {
size_t ic = the_warrior->instructions.size();
into.resize(sizeof(number_t) + sizeof(size_t) + sizeof(instruction) * ic);
memcpy(into.data(), &the_warrior->org, sizeof(number_t));
memcpy(into.data() + sizeof(number_t), &ic, sizeof(size_t));
memcpy(into.data() + sizeof(number_t) + sizeof(size_t),
the_warrior->instructions.data(), sizeof(instruction) * ic);
}
warrior *deserialize_warrior(const uint8_t *from) {
warrior *w = new warrior();
size_t ic;
memcpy(&w->org, from, sizeof(number_t));
memcpy(&ic, from + sizeof(number_t), sizeof(size_t));
w->instructions.resize(ic);
memcpy(w->instructions.data(),
from + sizeof(number_t) + sizeof(size_t), sizeof(instruction) * ic);
return w;
}
[[noreturn]] static void throw_compiler_exception(unsigned source_line_number, std::string message) {
compiler_exception ex;
ex.source_line_number = source_line_number;
@ -60,6 +82,7 @@ namespace lib94 {
//this abstract class represents expression fields extracted in stage 2 and evaluted in stage 4.
class expr {
public:
virtual ~expr() = default;
unsigned source_line_number;
number_t offset;
virtual intermediate_t evaluate(const label_offset_set &label_offsets) const = 0;
@ -68,6 +91,7 @@ namespace lib94 {
//this abstract class represents assertions fields extracted in stage 1 and evaluated in stage 3
class assertion {
public:
virtual ~assertion() = default;
unsigned source_line_number;
virtual bool check(const label_offset_set &label_offsets) const = 0;
};
@ -758,4 +782,18 @@ namespace lib94 {
}
warrior *compile_warrior_from_file(std::string path) {
std::ifstream file(path);
if (!file)
return 0;
std::stringstream stream;
stream << file.rdbuf();
file.close();
return compile_warrior(stream.str());
}
}

View file

@ -1,4 +1,4 @@
Copyright 2023 Benji Dial
Copyright 2023 - 2024 Benji Dial
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View file

@ -1,22 +1,20 @@
CPP_ARGS = -std=c++20 -O2 -Wall -Wextra -Iinclude -ggdb
# https://github.com/open-mpi/ompi/issues/5157
CPP_ARGS = -std=c++20 -O2 -Wall -Wextra -Iinclude -ggdb $(shell mpic++ --showme:compile) -DOMPI_SKIP_MPICXX
MPI_LD_ARGS = $(shell mpic++ --showme:link)
GTKMM_CPP_ARGS = $(shell pkg-config --cflags gtkmm-4.0)
GTKMM_LD_ARGS = $(shell pkg-config --libs gtkmm-4.0)
# https://github.com/open-mpi/ompi/issues/5157
MPI_CPP_ARGS = $(shell mpic++ --showme:compile) -DOMPI_SKIP_MPICXX
MPI_LD_ARGS = $(shell mpic++ --showme:link)
default: bin/bench bin/tabulator-mpi
default: bin/bench bin/tabulator
clean:
rm -r obj bin
bin/bench: obj/bench/main.o obj/bench/bench_window.o obj/bench/core_widget.o obj/lib94.o
@mkdir -p $(dir $@)
g++ ${CPP_ARGS} $^ ${GTKMM_LD_ARGS} -o $@
g++ ${CPP_ARGS} $^ ${MPI_LD_ARGS} ${GTKMM_LD_ARGS} -o $@
bin/tabulator-mpi: obj/tabulator-mpi/main.o obj/tabulator-mpi/head.o obj/tabulator-mpi/worker.o obj/lib94.o
bin/tabulator: obj/tabulator/tabulator.o obj/lib94.o
@mkdir -p $(dir $@)
g++ ${CPP_ARGS} $^ ${MPI_LD_ARGS} -o $@
@ -27,10 +25,6 @@ obj/bench/%.o: bench/%.cpp
@mkdir -p $(dir $@)
g++ -c ${CPP_ARGS} ${GTKMM_CPP_ARGS} $^ -o $@
obj/tabulator-mpi/%.o: tabulator-mpi/%.cpp
@mkdir -p $(dir $@)
g++ -c ${CPP_ARGS} ${MPI_CPP_ARGS} $^ -o $@
obj/%.o: %.cpp
@mkdir -p $(dir $@)
g++ -c ${CPP_ARGS} $^ -o $@

View file

@ -28,12 +28,9 @@ To open bench, just run bin/bench after building as above.
=== tabulator ===
The "tabulator" program runs every possible pairing of warriors from a selection against each other a number of times, and then shows
the number of wins of each warrior against each other warrior in a table format. This program uses MPI to run batches of these rounds
in different processes, and communicate the results back to a head process.
The "tabulator" program runs every possible pairing of warriors from a selection against each other with every possible separation
and turn order, and then shows the number of wins of each warrior against each other warrior in a table format. The column header
is the winner, and the row header is the loser. This program uses MPI to run this across multiple processes.
To run all of the included warriors against each other, run
mpirun bin/tabulator-mpi warriors/*.red
Note that tabulator expects at least two processes (one head process and at least one worker). If you only have one core, you may run
mpirun -np 2 --oversubscribe bin/tabulator-mpi warriors/*.red
mpirun bin/tabulator warriors/*.red

View file

@ -1,3 +0,0 @@
#define STEPS_TO_TIE 1000000
#define ROUNDS_PER_CHUNK 200
#define CHUNKS_PER_PAIR 5

View file

@ -1,112 +0,0 @@
#include <lib94/lib94.hpp>
#include <cstdio>
#include <mpi.h>
#include "constants.hpp"
static int **wins;
static int get_result() {
int result[4];
MPI_Status status;
MPI_Recv(result, 4, MPI_INT, MPI_ANY_SOURCE, 1, MPI_COMM_WORLD, &status);
if (result[0] != -1) {
wins[result[0]][result[1]] += result[2];
wins[result[1]][result[0]] += result[3];
}
return status.MPI_SOURCE;
}
void head_main(int comm_size, int warrior_count, const lib94::warrior *const *warriors) {
wins = new int *[warrior_count];
for (int i = 0; i < warrior_count; ++i) {
wins[i] = new int[warrior_count];
for (int j = 0; j < warrior_count; ++j)
wins[i][j] = 0;
}
int chunks = warrior_count * (warrior_count - 1) / 2 * CHUNKS_PER_PAIR;
int on_chunk = 0;
int right_name_width = 0;
for (int i = 0; i < warrior_count; ++i)
if ((int)warriors[i]->name.size() > right_name_width)
right_name_width = warriors[i]->name.size();
int rank_width = std::max(std::string("rank").size(), std::to_string(comm_size - 1).size());
int right_round_width = std::to_string(CHUNKS_PER_PAIR * ROUNDS_PER_CHUNK).size();
int right_chunk_width = std::to_string(chunks).size();
int left_name_width = std::max(right_name_width, (int)std::string("match").size() - right_name_width - 4);
int left_round_width = std::max((int)std::to_string((CHUNKS_PER_PAIR - 1) * ROUNDS_PER_CHUNK + 1).size(), (int)std::string("rounds").size() - right_round_width - 3);
int left_chunk_width = std::max(right_chunk_width, (int)std::string("chunk").size() - right_chunk_width - 3);
fprintf(stderr, "\x1b""7\x1b[?47h\x1b[?25l\x1b[2J\x1b[0H");
fprintf(stderr, "%*s | %*s | %*s | %*s", rank_width, "rank",
left_name_width + 4 + right_name_width, "match",
left_round_width + 3 + right_round_width, "rounds",
left_chunk_width + 3 + right_chunk_width, "chunk");
for (int i = 0; i < warrior_count; ++i)
for (int j = i + 1; j < warrior_count; ++j)
for (int x = 0; x < CHUNKS_PER_PAIR; ++x) {
++on_chunk;
int rank = get_result();
int message[4] = {i, j, x * ROUNDS_PER_CHUNK};
MPI_Send(message, 4, MPI_INT, rank, 0, MPI_COMM_WORLD);
fprintf(stderr, "\x1b[%d;0H%*d | %*s vs %*s | %*d - %*d | %*d / %*d\x1b[0K", rank + 1, rank_width, rank,
left_name_width, warriors[i]->name.c_str(), right_name_width, warriors[j]->name.c_str(),
left_round_width, x * ROUNDS_PER_CHUNK + 1, right_round_width, x * ROUNDS_PER_CHUNK + ROUNDS_PER_CHUNK,
left_chunk_width, on_chunk, right_chunk_width, chunks);
}
for (int i = 0; i < comm_size - 1; ++i) {
int rank = get_result();
int message[4] = {-1};
MPI_Send(message, 4, MPI_INT, rank, 0, MPI_COMM_WORLD);
fprintf(stderr, "\x1b[%d;0H%*d | %*s | %*s | %*s\x1b[0K",
rank + 1, rank_width, rank,
left_name_width + 4 + right_name_width, "",
left_round_width + 3 + right_round_width, "",
left_chunk_width + 3 + right_chunk_width, "done");
}
fprintf(stderr, "\x1b[?25h\x1b[?47l\x1b""8");
int *tab_widths = new int[warrior_count + 1];
tab_widths[0] = 0;
for (int i = 0; i < warrior_count; ++i) {
int len = warriors[i]->name.size();
if (len > tab_widths[0])
tab_widths[0] = len;
tab_widths[i + 1] = len > 5 ? len : 5;
}
printf(" %*s", tab_widths[0], "");
for (int j = 0; j < warrior_count; ++j)
printf(" | %*s", tab_widths[j + 1], warriors[j]->name.c_str());
putchar('\n');
putchar('-');
for (int x = 0; x < tab_widths[0]; ++x)
putchar('-');
for (int j = 0; j < warrior_count; ++j) {
printf("-+-");
for (int x = 0; x < tab_widths[j + 1]; ++x)
putchar('-');
}
printf("-\n");
for (int i = 0; i < warrior_count; ++i) {
printf(" %*s", tab_widths[0], warriors[i]->name.c_str());
for (int j = 0; j < warrior_count; ++j)
if (i == j)
printf(" | %*s", tab_widths[j + 1], "x");
else
printf(" | %*d", tab_widths[j + 1], wins[i][j]);
putchar('\n');
}
}

View file

@ -1,60 +0,0 @@
#include <lib94/lib94.hpp>
#include <fstream>
#include <cstdio>
#include <mpi.h>
void head_main(int comm_size, int warrior_count, const lib94::warrior *const *warriors);
void worker_main(const lib94::warrior *const *warriors);
const lib94::warrior *load_warrior(const char *file) {
std::ifstream stream(file);
if (!stream) {
fprintf(stderr, "could not open %s\n", file);
exit(1);
}
std::string source(std::istreambuf_iterator<char>(stream), {});
try {
return lib94::compile_warrior(source);
}
catch (const lib94::compiler_exception &ex) {
fprintf(stderr, "error in %s on line %u: %s\n", file, ex.source_line_number, ex.message.c_str());
exit(1);
}
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int comm_rank, comm_size;
MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
if (comm_size < 2) {
fprintf(stderr, "at least two processes are required\n");
return 1;
}
const lib94::warrior **warriors = new const lib94::warrior *[argc - 1];
for (int i = 0; i < argc - 1; ++i)
warriors[i] = load_warrior(argv[i + 1]);
for (int i = 0; i < argc - 1; ++i)
for (int j = i + 1; j < argc - 1; ++j) {
const lib94::warrior *wbuf[2] = {warriors[i], warriors[j]};
if (!lib94::init_round(wbuf, 2)) {
fprintf(stderr, "warriors do not fit in core\n");
return 1;
}
}
if (comm_rank == 0)
head_main(comm_size, argc - 1, warriors);
else
worker_main(warriors);
MPI_Finalize();
}

View file

@ -1,51 +0,0 @@
#include <lib94/lib94.hpp>
#include <cassert>
#include <ctime>
#include <mpi.h>
#include "constants.hpp"
static void do_round(const lib94::warrior *w1, const lib94::warrior *w2, int &w1_wins, int& w2_wins) {
const lib94::warrior *ws[2] = {w1, w2};
lib94::instruction background = {
.op = lib94::DAT, .mod = lib94::F,
.amode = lib94::DIRECT, .bmode = lib94::DIRECT,
.anumber = 0, .bnumber = 0
};
lib94::clear_core(background);
assert(lib94::init_round(ws, 2));
for (int i = 0; i < STEPS_TO_TIE; ++i) {
const lib94::warrior *result = lib94::single_step<false>();
if (result == w1) {
++w2_wins;
return;
}
if (result == w2) {
++w1_wins;
return;
}
}
}
void worker_main(const lib94::warrior *const *warriors) {
lib94::seed_prng(time(0));
int buffer[4] = {-1};
while (1) {
MPI_Send(buffer, 4, MPI_INT, 0, 1, MPI_COMM_WORLD);
MPI_Recv(buffer, 4, MPI_INT, 0, 0, MPI_COMM_WORLD, 0);
if (buffer[0] == -1)
return;
buffer[2] = 0;
buffer[3] = 0;
for (int i = 0; i < ROUNDS_PER_CHUNK; ++i)
do_round(warriors[buffer[0]], warriors[buffer[1]], buffer[2], buffer[3]);
}
}

159
tabulator/tabulator.cpp Normal file
View file

@ -0,0 +1,159 @@
#include <lib94/lib94.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdio>
#include <vector>
const int steps_to_tie = 1000000;
int error(std::string msg, int rank) {
if (rank == 0) {
std::cerr << msg << std::endl;
return 1;
}
return 0;
}
constexpr lib94::instruction background_instruction = {
.op = lib94::DAT, .mod = lib94::F,
.amode = lib94::DIRECT, .bmode = lib94::DIRECT,
.anumber = 0, .bnumber = 0
};
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int rank;
int size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
lib94::seed_prng(time(0) + rank);
std::vector<std::string> filenames = {};
for (int i = 1; i < argc; ++i)
filenames.push_back(argv[i]);
if (filenames.size() == 0)
return error("no files specified.", rank);
if (filenames.size() == 1)
return error("only one file specified.", rank);
int count = filenames.size();
lib94::warrior **warriors = new lib94::warrior *[count];
for (int i = 0; i < count; ++i) {
try {
warriors[i] = lib94::compile_warrior_from_file(filenames[i]);
}
catch (const lib94::compiler_exception &ex) {
return error("could not compile " + filenames[i] + ": " + ex.message + " on line " + std::to_string(ex.source_line_number) + ".", rank);
}
if (!warriors[i])
return error("could not open " + filenames[i] + ".", rank);
}
int **wins_against = new int *[count];
for (int i = 0; i < count; ++i)
wins_against[i] = new int[count];
int **rounds_against = new int *[count];
for (int i = 0; i < count; ++i)
rounds_against[i] = new int[count];
for (int i = 0; i < count; ++i)
for (int j = i + 1; j < count; ++j) {
const lib94::warrior *w1 = warriors[i];
const lib94::warrior *w2 = warriors[j];
if (rank == 0)
std::cout << "running " << w1->name << " vs " << w2->name << "..." << std::endl;
int w1_wins, w2_wins, rounds;
lib94::tabulate_mpi<true>(background_instruction, w1, w2, steps_to_tie, w1_wins, w2_wins, rounds, MPI_COMM_WORLD);
if (rank == 0)
std::cout
<< "results:\n " << w1->name << ": " << w1_wins << "\n "
<< w2->name << ": " << w2_wins << "\n ties: "
<< (rounds - w1_wins - w2_wins) << "\n\n";
wins_against[i][j] = w1_wins;
wins_against[j][i] = w2_wins;
rounds_against[i][j] = rounds;
rounds_against[j][i] = rounds;
}
if (rank == 0) {
unsigned column_width = 0;
unsigned name_width = 0;
for (int i = 0; i < count; ++i)
if (warriors[i]->name.size() > name_width) {
name_width = warriors[i]->name.size();
column_width = name_width;
}
unsigned number_width = 0;
for (int i = 0; i < count; ++i)
for (int j = 0; j < count; ++j)
if (i != j) {
unsigned width = std::to_string(rounds_against[i][j]).size();
if (width > number_width)
number_width = width;
if (width * 2 + 3 > column_width)
column_width = width * 2 + 3;
}
for (int i = 0; i < count; ++i)
warriors[i]->name.insert(0, column_width - warriors[i]->name.size(), ' ');
std::string spaces, dashes;
spaces.resize(column_width, ' ');
dashes.resize(column_width, '-');
std::string first_spaces;
first_spaces.resize(name_width, ' ');
std::cout << ' ' << first_spaces;
for (int i = 0; i < count; ++i) {
std::cout << " | " << warriors[i]->name;
}
std::string first_dashes;
first_dashes.resize(name_width, '-');
std::cout << "\n-" << first_dashes;
for (int i = 0; i < count; ++i)
std::cout << "-+-" << dashes;
std::cout << "-\n";
for (int i = 0; i < count; ++i) {
std::string name = warriors[i]->name;
name.erase(0, column_width - name_width);
std::cout << ' ' << name;
for (int j = 0; j < count; ++j)
if (i == j)
std::cout << " | " << spaces;
else {
std::string num = std::to_string(wins_against[j][i]);
std::string den = std::to_string(rounds_against[j][i]);
num.insert(0, number_width - num.size(), ' ');
den.insert(0, number_width - den.size(), ' ');
std::string content = num + " / " + den;
content.insert(0, column_width - content.size(), ' ');
std::cout << " | " << content;
}
std::cout << '\n';
}
}
MPI_Finalize();
return 0;
}