Disabled external gits
This commit is contained in:
Submodule cs453-ca/CS453-2021-project deleted from ad9dbfc13b
30
cs453-ca/CS453-2021-project/.gitignore
vendored
Normal file
30
cs453-ca/CS453-2021-project/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Ignore everything in this directory and subdirectories
|
||||
*
|
||||
|
||||
# Except directories and specific files
|
||||
!*/
|
||||
|
||||
!.gitignore
|
||||
!*.md
|
||||
|
||||
!.placeholder
|
||||
|
||||
!Makefile
|
||||
|
||||
!*.c
|
||||
!*.C
|
||||
!*.cc
|
||||
!*.cpp
|
||||
!*.cxx
|
||||
!*.c++
|
||||
|
||||
!*.h
|
||||
!*.hh
|
||||
!*.hpp
|
||||
!*.hxx
|
||||
!*.h++
|
||||
|
||||
!*.py
|
||||
|
||||
# And of course, we do not leak the solution proposed by the TA :)
|
||||
solution/
|
27
cs453-ca/CS453-2021-project/README.md
Normal file
27
cs453-ca/CS453-2021-project/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# CS-453 - Course project
|
||||
|
||||
The [project description](https://dcl.epfl.ch/site/_media/education/ca-project.pdf) is available on [Moodle](https://moodle.epfl.ch/course/view.php?id=14334) and the [website of the course](https://dcl.epfl.ch/site/education/ca_2021).
|
||||
|
||||
The description includes:
|
||||
* an introduction to (software) transactional memory
|
||||
* an introduction to concurrent programming in C11/C++11, with pointers to more resources
|
||||
* the _specifications_ of the transactional memory you have to implement, i.e. both:
|
||||
* sufficient properties for a transactional memory to be deemed _correct_
|
||||
* a thorough description of the transactional memory interface
|
||||
* practical informations, including:
|
||||
* how to test your implementation on your local machine and on the evaluation server
|
||||
* how your submission will be graded
|
||||
* rules for (optionally) using 3rd-party libraries and collaboration (although the project is _individual_)
|
||||
|
||||
This repository provides:
|
||||
* examples of how to use synchronization primitives (in `sync-examples/`)
|
||||
* a reference implementation (in `reference/`)
|
||||
* a "skeleton" implementation (in `template/`)
|
||||
* this template is written in C11
|
||||
* feel free to overwrite it completely if you prefer to use C++ (in this case include `<tm.hpp>` instead of `<tm.h>`)
|
||||
* the program that will test your implementation (in `grading/`)
|
||||
* the same program will be used on the evaluation server (although possibly with a different seed)
|
||||
* you can use it to test/debug your implementation on your local machine (see the [description](https://dcl.epfl.ch/site/_media/education/ca-project.pdf))
|
||||
* a tool to submit your implementation (in `submit.py`)
|
||||
* you should have received by mail a secret _unique user identifier_ (UUID)
|
||||
* see the [description](https://dcl.epfl.ch/site/_media/education/ca-project.pdf) for more information
|
55
cs453-ca/CS453-2021-project/grading/Makefile
Normal file
55
cs453-ca/CS453-2021-project/grading/Makefile
Normal file
@@ -0,0 +1,55 @@
|
||||
BIN := ./$(notdir $(lastword $(abspath .)))
|
||||
|
||||
EXT_H := h
|
||||
EXT_HPP := h hh hpp hxx h++
|
||||
EXT_C := c
|
||||
EXT_CXX := C cc cpp cxx c++
|
||||
|
||||
INCLUDE_DIRS := ../include .
|
||||
SOURCE_DIRS := .
|
||||
|
||||
WILD_EXT = $(strip $(foreach EXT,$($(1)),$(wildcard $(2)/*.$(EXT))))
|
||||
|
||||
HDRS_C := $(foreach INCLUDE_DIR,$(INCLUDE_DIRS),$(call WILD_EXT,EXT_H,$(INCLUDE_DIR)))
|
||||
HDRS_CXX := $(foreach INCLUDE_DIR,$(INCLUDE_DIRS),$(call WILD_EXT,EXT_HPP,$(INCLUDE_DIR)))
|
||||
SRCS_C := $(foreach SOURCE_DIR,$(SOURCE_DIRS),$(call WILD_EXT,EXT_C,$(SOURCE_DIR)))
|
||||
SRCS_CXX := $(foreach SOURCE_DIR,$(SOURCE_DIRS),$(call WILD_EXT,EXT_CXX,$(SOURCE_DIR)))
|
||||
OBJS := $(SRCS_C:%=%.o) $(SRCS_CXX:%=%.o)
|
||||
|
||||
CC := $(CC)
|
||||
CCFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c11 $(foreach INCLUDE_DIR,$(INCLUDE_DIRS),-I$(INCLUDE_DIR))
|
||||
CXX := $(CXX)
|
||||
CXXFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c++17 $(foreach INCLUDE_DIR,$(INCLUDE_DIRS),-I$(INCLUDE_DIR))
|
||||
LD := $(if $(SRCS_CXX),$(CXX),$(CC))
|
||||
LDFLAGS :=
|
||||
LDLIBS := -ldl -lpthread
|
||||
|
||||
LIB_DIRS := $(filter-out ../include/ ../grading/ ../playground/ ../template/ ../sync-examples/,$(filter-out $(wildcard ../*),$(wildcard ../*/)))
|
||||
LIB_SOS := $(patsubst %/,%.so,$(filter-out ../reference/,$(LIB_DIRS)))
|
||||
|
||||
.PHONY: build build-libs clean clean-libs run
|
||||
|
||||
build: $(BIN)
|
||||
build-libs:
|
||||
@$(foreach DIR,$(LIB_DIRS),make -C $(DIR) build; )
|
||||
clean:
|
||||
$(RM) $(OBJS) $(BIN)
|
||||
clean-libs:
|
||||
@$(foreach DIR,$(LIB_DIRS),make -C $(DIR) clean; )
|
||||
run: $(BIN)
|
||||
$(BIN) 453 ../reference.so $(LIB_SOS)
|
||||
|
||||
define BUILD_C
|
||||
%.$(1).o: %.$(1) $$(HDRS_C) Makefile
|
||||
$$(CC) $$(CCFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_C),$(eval $(call BUILD_C,$(EXT))))
|
||||
|
||||
define BUILD_CXX
|
||||
%.$(1).o: %.$(1) $$(HDRS_CXX) Makefile
|
||||
$$(CXX) $$(CXXFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_CXX),$(eval $(call BUILD_CXX,$(EXT))))
|
||||
|
||||
$(BIN): $(OBJS) Makefile
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
|
333
cs453-ca/CS453-2021-project/grading/common.hpp
Normal file
333
cs453-ca/CS453-2021-project/grading/common.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* @file common.hpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Common set of helper functions/classes.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// External headers
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
extern "C" {
|
||||
#include <time.h>
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Define a proposition as likely true.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef likely
|
||||
#ifdef __GNUC__
|
||||
#define likely(prop) \
|
||||
__builtin_expect((prop) ? 1 : 0, 1)
|
||||
#else
|
||||
#define likely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
/** Define a proposition as likely false.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef unlikely
|
||||
#ifdef __GNUC__
|
||||
#define unlikely(prop) \
|
||||
__builtin_expect((prop) ? 1 : 0, 0)
|
||||
#else
|
||||
#define unlikely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
// Whether to enable more safety checks
|
||||
constexpr static auto assert_mode = false;
|
||||
|
||||
// Maximum waiting time for initialization/clean-ups (in ms)
|
||||
constexpr static auto max_side_time = ::std::chrono::milliseconds{2000};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
namespace Exception {
|
||||
|
||||
/** Defines a simple exception.
|
||||
* @param name Exception name
|
||||
* @param parent Parent exception (use 'Any' as the root)
|
||||
* @param deftxt Default explanatory string
|
||||
**/
|
||||
#define EXCEPTION(name, parent, deftxt) \
|
||||
class name: public parent { \
|
||||
public: \
|
||||
/** Default explanatory string constructor. \
|
||||
**/ \
|
||||
name(): parent{deftxt} {} \
|
||||
/** Non-default parent forwarding constructor. \
|
||||
* @param ... Forwarded arguments \
|
||||
**/ \
|
||||
template<class... Args> name(Args&&... args): parent{::std::forward<Args>(args)...} {} \
|
||||
}
|
||||
|
||||
/** Root user exception class.
|
||||
**/
|
||||
class Any: public ::std::exception {
|
||||
protected:
|
||||
char const* text; // Explanation string
|
||||
public:
|
||||
/** Explanatory string constructor.
|
||||
* @param text Explanatory null-terminated string
|
||||
**/
|
||||
Any(char const* text): text{text} {}
|
||||
public:
|
||||
/** Return the explanatory string.
|
||||
* @return Explanatory string
|
||||
**/
|
||||
virtual char const* what() const noexcept {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
/** Exception tree.
|
||||
**/
|
||||
EXCEPTION(Unreachable, Any, "unreachable code reached");
|
||||
EXCEPTION(Bounded, Any, "bounded execution exception");
|
||||
EXCEPTION(BoundedOverrun, Any, "bounded execution overrun");
|
||||
|
||||
}
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Non-copyable helper base class.
|
||||
**/
|
||||
class NonCopyable {
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
NonCopyable(NonCopyable const&) = delete;
|
||||
NonCopyable& operator=(NonCopyable const&) = delete;
|
||||
protected:
|
||||
/** Protected default constructor, to make sure class is not directly instantiated.
|
||||
**/
|
||||
NonCopyable() = default;
|
||||
};
|
||||
|
||||
/** Time accounting class.
|
||||
**/
|
||||
class Chrono final {
|
||||
public:
|
||||
/** Tick class (always 1 tick = 1 ns).
|
||||
**/
|
||||
using Tick = uint_fast64_t;
|
||||
constexpr static auto invalid_tick = Tick{0xbadc0de}; // Invalid tick value
|
||||
private:
|
||||
Tick total; // Total tick counter
|
||||
Tick local; // Segment tick counter
|
||||
public:
|
||||
/** Tick constructor.
|
||||
* @param tick Initial number of ticks (optional)
|
||||
**/
|
||||
Chrono(Tick tick = 0) noexcept: total{tick} {}
|
||||
private:
|
||||
/** Call a "clock" function, convert the result to the Tick type.
|
||||
* @param func "Clock" function to call
|
||||
* @return Resulting time
|
||||
**/
|
||||
static Tick convert(int (*func)(::clockid_t, struct ::timespec*)) noexcept {
|
||||
struct ::timespec buf;
|
||||
if (unlikely(func(CLOCK_MONOTONIC, &buf) < 0))
|
||||
return invalid_tick;
|
||||
auto res = static_cast<Tick>(buf.tv_nsec) + static_cast<Tick>(buf.tv_sec) * static_cast<Tick>(1000000000ul);
|
||||
if (unlikely(res == invalid_tick)) // Bad luck...
|
||||
return invalid_tick + 1;
|
||||
return res;
|
||||
}
|
||||
public:
|
||||
/** Get the resolution of the clock used.
|
||||
* @return Resolution (in ns), 'invalid_tick' for unknown
|
||||
**/
|
||||
static auto get_resolution() noexcept {
|
||||
return convert(::clock_getres);
|
||||
}
|
||||
public:
|
||||
/** Start measuring a time segment.
|
||||
**/
|
||||
void start() noexcept {
|
||||
local = convert(::clock_gettime);
|
||||
}
|
||||
/** Measure a time segment.
|
||||
**/
|
||||
auto delta() noexcept {
|
||||
return convert(::clock_gettime) - local;
|
||||
}
|
||||
/** Stop measuring a time segment, and add it to the total.
|
||||
**/
|
||||
void stop() noexcept {
|
||||
total += delta();
|
||||
}
|
||||
/** Reset the total tick counter.
|
||||
**/
|
||||
void reset() noexcept {
|
||||
total = 0;
|
||||
}
|
||||
/** Get the total tick counter.
|
||||
* @return Total tick counter
|
||||
**/
|
||||
auto get_tick() const noexcept {
|
||||
return total;
|
||||
}
|
||||
};
|
||||
|
||||
/** Atomic waitable latch class.
|
||||
**/
|
||||
class Latch final {
|
||||
private:
|
||||
::std::mutex lock; // Local lock
|
||||
::std::condition_variable cv; // For waiting/waking up
|
||||
bool raised; // State of the latch
|
||||
public:
|
||||
/** Deleted copy/move constructor/assignment.
|
||||
**/
|
||||
Latch(Latch const&) = delete;
|
||||
Latch& operator=(Latch const&) = delete;
|
||||
/** Initial state constructor.
|
||||
* @param raised Initial state of the latch
|
||||
**/
|
||||
Latch(bool raised = false): lock{}, raised{raised} {}
|
||||
public:
|
||||
/** Raise the latch, no-op if already raised, release semantic.
|
||||
**/
|
||||
void raise() {
|
||||
// Release fence
|
||||
::std::atomic_thread_fence(::std::memory_order_release);
|
||||
// Raise
|
||||
::std::unique_lock<decltype(lock)> guard{lock};
|
||||
raised = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
/** Wait for the latch to be raised, then reset it, acquire semantic if no timeout.
|
||||
* @param maxtick Maximal duration to wait for (in ticks)
|
||||
* @return Whether the latch was raised before the maximal duration elapsed
|
||||
**/
|
||||
bool wait(Chrono::Tick maxtick) {
|
||||
{ // Wait for raised
|
||||
::std::unique_lock<decltype(lock)> guard{lock};
|
||||
// Wait
|
||||
if (maxtick == Chrono::invalid_tick) {
|
||||
while (!raised)
|
||||
cv.wait(guard);
|
||||
} else {
|
||||
if (!cv.wait_for(guard, ::std::chrono::nanoseconds{maxtick}, [&]() { return raised; })) // Overtime
|
||||
return false;
|
||||
}
|
||||
// Reset
|
||||
raised = false;
|
||||
}
|
||||
// Acquire fence
|
||||
::std::atomic_thread_fence(::std::memory_order_acquire);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Pause execution for a "short" period of time.
|
||||
**/
|
||||
static void short_pause() {
|
||||
#if (defined(__i386__) || defined(__x86_64__)) && defined(USE_MM_PAUSE)
|
||||
_mm_pause();
|
||||
#else
|
||||
::std::this_thread::yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Run some function for some bounded time, throws 'Exception::BoundedOverrun' on overtime.
|
||||
* @param dur Maximum execution duration
|
||||
* @param func Function to run (void -> void)
|
||||
* @param emsg Null-terminated error message
|
||||
**/
|
||||
template<class Rep, class Period, class Func> static void bounded_run(::std::chrono::duration<Rep, Period> const& dur, Func&& func, char const* emsg) {
|
||||
::std::mutex lock;
|
||||
::std::unique_lock<decltype(lock)> guard{lock};
|
||||
::std::condition_variable cv;
|
||||
::std::thread runner{[&]() {
|
||||
func();
|
||||
{ // Notify master
|
||||
::std::unique_lock<decltype(lock)> guard{lock};
|
||||
cv.notify_all();
|
||||
}
|
||||
}};
|
||||
if (cv.wait_for(guard, dur) != std::cv_status::no_timeout) {
|
||||
runner.detach();
|
||||
throw Exception::BoundedOverrun{emsg};
|
||||
}
|
||||
runner.join();
|
||||
}
|
||||
|
||||
/** Spin barrier class.
|
||||
**/
|
||||
class Barrier final {
|
||||
public:
|
||||
/** Counter class.
|
||||
**/
|
||||
using Counter = uint_fast32_t;
|
||||
/** Mode enum class.
|
||||
**/
|
||||
enum class Mode {
|
||||
enter,
|
||||
leave
|
||||
};
|
||||
private:
|
||||
Counter cardinal; // Total number of threads that synchronize
|
||||
::std::atomic<Counter> mutable step; // Step counters
|
||||
::std::atomic<Mode> mutable mode; // Current mode
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
Barrier(Barrier const&) = delete;
|
||||
Barrier& operator=(Barrier const&) = delete;
|
||||
/** Number of threads constructor.
|
||||
* @param cardinal Non-null total number of threads synchronizing on this barrier
|
||||
**/
|
||||
Barrier(Counter cardinal): cardinal{cardinal}, step{0}, mode{Mode::enter} {}
|
||||
public:
|
||||
/** [thread-safe] Synchronize all the threads.
|
||||
**/
|
||||
void sync() const {
|
||||
// Enter
|
||||
if (step.fetch_add(1, ::std::memory_order_relaxed) + 1 == cardinal) { // Set leave mode
|
||||
mode.store(Mode::leave, ::std::memory_order_release);
|
||||
} else { // Wait for leave mode
|
||||
while (unlikely(mode.load(::std::memory_order_acquire) != Mode::leave))
|
||||
short_pause();
|
||||
}
|
||||
// Leave
|
||||
if (step.fetch_sub(1, ::std::memory_order_relaxed) - 1 == 0) { // Set enter mode
|
||||
mode.store(Mode::enter, ::std::memory_order_release);
|
||||
} else { // Wait for enter mode
|
||||
while (unlikely(mode.load(::std::memory_order_acquire) != Mode::enter))
|
||||
short_pause();
|
||||
}
|
||||
}
|
||||
};
|
353
cs453-ca/CS453-2021-project/grading/grading.cpp
Normal file
353
cs453-ca/CS453-2021-project/grading/grading.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* @file grading.cpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Grading of the implementations.
|
||||
**/
|
||||
|
||||
// External headers
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <variant>
|
||||
|
||||
// Internal headers
|
||||
#include "common.hpp"
|
||||
#include "transactional.hpp"
|
||||
#include "workload.hpp"
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Tailored thread synchronization class.
|
||||
**/
|
||||
class Sync final {
|
||||
private:
|
||||
/** Synchronization status.
|
||||
**/
|
||||
enum class Status {
|
||||
Wait, // Workers waiting each others, run as soon as all ready
|
||||
Run, // Workers running (still full success)
|
||||
Abort, // Workers running (>0 failure)
|
||||
Done, // Workers done (all success)
|
||||
Fail, // Workers done (>0 failure)
|
||||
Quit // Workers must terminate
|
||||
};
|
||||
private:
|
||||
unsigned int const nbworkers; // Number of workers to support
|
||||
::std::atomic<unsigned int> nbready; // Number of thread having reached that state
|
||||
::std::atomic<Status> status; // Current synchronization status
|
||||
::std::atomic<char const*> errmsg; // Any one of the error message(s)
|
||||
Chrono runtime; // Runtime between 'master_notify' and when the last worker finished
|
||||
Latch donelatch; // For synchronization last worker -> master
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
Sync(Sync const&) = delete;
|
||||
Sync& operator=(Sync const&) = delete;
|
||||
/** Worker count constructor.
|
||||
* @param nbworkers Number of workers to support
|
||||
**/
|
||||
Sync(unsigned int nbworkers): nbworkers{nbworkers}, nbready{0}, status{Status::Done}, errmsg{nullptr} {}
|
||||
public:
|
||||
/** Master trigger "synchronized" execution in all threads (instead of joining).
|
||||
**/
|
||||
void master_notify() noexcept {
|
||||
status.store(Status::Wait, ::std::memory_order_relaxed);
|
||||
runtime.start();
|
||||
}
|
||||
/** Master trigger termination in all threads (instead of notifying).
|
||||
**/
|
||||
void master_join() noexcept {
|
||||
status.store(Status::Quit, ::std::memory_order_relaxed);
|
||||
}
|
||||
/** Master wait for all workers to finish.
|
||||
* @param maxtick Maximum number of ticks to wait before exiting the process on an error (optional, 'invalid_tick' for none)
|
||||
* @return Total execution time on success, or error constant null-terminated string on failure
|
||||
**/
|
||||
::std::variant<Chrono, char const*> master_wait(Chrono::Tick maxtick = Chrono::invalid_tick) {
|
||||
// Wait for all worker threads, synchronize-with the last one
|
||||
if (!donelatch.wait(maxtick))
|
||||
throw Exception::BoundedOverrun{"Transactional library takes too long to process the transactions"};
|
||||
// Return runtime on success, of error message on failure
|
||||
switch (status.load(::std::memory_order_relaxed)) {
|
||||
case Status::Done:
|
||||
return runtime;
|
||||
case Status::Fail:
|
||||
return errmsg;
|
||||
default:
|
||||
throw Exception::Unreachable{"Master woke after raised latch, no timeout, but unexpected status"};
|
||||
}
|
||||
}
|
||||
/** Worker spin-wait until next run.
|
||||
* @return Whether the worker can proceed, or quit otherwise
|
||||
**/
|
||||
bool worker_wait() noexcept {
|
||||
while (true) {
|
||||
auto res = status.load(::std::memory_order_relaxed);
|
||||
if (res == Status::Wait)
|
||||
break;
|
||||
if (res == Status::Quit)
|
||||
return false;
|
||||
short_pause();
|
||||
}
|
||||
auto res = nbready.fetch_add(1, ::std::memory_order_relaxed);
|
||||
if (res + 1 == nbworkers) { // Latest worker, switch to run status
|
||||
nbready.store(0, ::std::memory_order_relaxed);
|
||||
status.store(Status::Run, ::std::memory_order_release); // Synchronize-with previous worker waiting for run/abort state
|
||||
} else do { // Not latest worker, wait for run status
|
||||
short_pause();
|
||||
auto res = status.load(::std::memory_order_acquire); // Synchronize-with latest worker switching to run/abort state
|
||||
if (res == Status::Run || res == Status::Abort)
|
||||
break;
|
||||
} while (true);
|
||||
return true;
|
||||
}
|
||||
/** Worker notify termination of its run.
|
||||
* @param error Error constant null-terminated string ('nullptr' for none)
|
||||
**/
|
||||
void worker_notify(char const* error) noexcept {
|
||||
if (error) {
|
||||
errmsg.store(error, ::std::memory_order_relaxed);
|
||||
status.store(Status::Abort, ::std::memory_order_relaxed);
|
||||
}
|
||||
auto&& res = nbready.fetch_add(1, ::std::memory_order_acq_rel); // Synchronize-with previous worker(s) potentially setting aborted status
|
||||
if (res + 1 == nbworkers) { // Latest worker, switch to done/fail status
|
||||
nbready.store(0, ::std::memory_order_relaxed);
|
||||
status.store(status.load(::std::memory_order_relaxed) == Status::Abort ? Status::Fail : Status::Done, ::std::memory_order_relaxed);
|
||||
runtime.stop();
|
||||
donelatch.raise(); // Synchronize-with 'master_wait'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Measure the arithmetic mean of the execution time of the given workload with the given transaction library.
|
||||
* @param workload Workload instance to use
|
||||
* @param nbthreads Number of concurrent threads to use
|
||||
* @param nbrepeats Number of repetitions (keep the median)
|
||||
* @param seed Seed to use for performance measurements
|
||||
* @param maxtick_init Timeout for (re)initialization ('Chrono::invalid_tick' for none)
|
||||
* @param maxtick_perf Timeout for performance measurements ('Chrono::invalid_tick' for none)
|
||||
* @param maxtick_chck Timeout for correctness check ('Chrono::invalid_tick' for none)
|
||||
* @return Error constant null-terminated string ('nullptr' for none), execution times (in ns) (undefined if inconsistency detected)
|
||||
**/
|
||||
static auto measure(Workload& workload, unsigned int const nbthreads, unsigned int const nbrepeats, Seed seed, Chrono::Tick maxtick_init, Chrono::Tick maxtick_perf, Chrono::Tick maxtick_chck) {
|
||||
::std::vector<::std::thread> threads(nbthreads);
|
||||
::std::mutex cerrlock; // To avoid interleaving writes to 'cerr' in case more than one thread throw
|
||||
Sync sync{nbthreads}; // "As-synchronized-as-possible" starts so that threads interfere "as-much-as-possible"
|
||||
|
||||
// We start nbthreads threads to measure performance.
|
||||
for (unsigned int i = 0; i < nbthreads; ++i) { // Start threads
|
||||
try {
|
||||
threads[i] = ::std::thread{[&](unsigned int i) {
|
||||
// This is the workload that all threads run simulataneously.
|
||||
// It is devided into a series of small tests. Each test is specified in workload.hpp.
|
||||
// Threads are synchronized between each test so that they run with a lot of concurrency.
|
||||
try {
|
||||
// 1. Initialization
|
||||
if (!sync.worker_wait()) return; // Sync. of threads
|
||||
sync.worker_notify(workload.init()); // Runs the test and tells the master about errors
|
||||
|
||||
// 2. Performance measurements
|
||||
for (unsigned int count = 0; count < nbrepeats; ++count) {
|
||||
if (!sync.worker_wait()) return;
|
||||
sync.worker_notify(workload.run(i, seed + nbthreads * count + i));
|
||||
}
|
||||
|
||||
// 3. Correctness check
|
||||
if (!sync.worker_wait()) return;
|
||||
sync.worker_notify(workload.check(i, std::random_device{}())); // Random seed is wanted here
|
||||
|
||||
// Synchronized quit
|
||||
if (!sync.worker_wait()) return;
|
||||
throw Exception::Unreachable{"unexpected worker iteration after checks"};
|
||||
} catch (::std::exception const& err) {
|
||||
sync.worker_notify("Internal worker exception(s)"); // Exception post-'Sync::worker_wait' (i.e. in 'Workload::run' or 'Workload::check'), since 'Sync::worker_*' do not throw
|
||||
{ // Print the error
|
||||
::std::unique_lock<decltype(cerrlock)> guard{cerrlock};
|
||||
::std::cerr << "⎪⎧ *** EXCEPTION ***" << ::std::endl << "⎪⎩ " << err.what() << ::std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, i};
|
||||
} catch (...) {
|
||||
for (unsigned int j = 0; j < i; ++j) // Detach threads to avoid termination due to attached thread going out of scope
|
||||
threads[j].detach();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the master that synchronizes the worker threads.
|
||||
// It basically triggers each step seen above.
|
||||
// After all tests succeed, it returns the time it took to run each test.
|
||||
// It returns early in case of a failure.
|
||||
try {
|
||||
char const* error = nullptr;
|
||||
Chrono::Tick time_init = Chrono::invalid_tick;
|
||||
Chrono::Tick times[nbrepeats];
|
||||
Chrono::Tick time_chck = Chrono::invalid_tick;
|
||||
auto const posmedian = nbrepeats / 2;
|
||||
{ // Initialization (with cheap correctness test)
|
||||
sync.master_notify(); // We tell workers to start working.
|
||||
auto res = sync.master_wait(maxtick_init); // If running the student's version, it will timeout if way slower than the reference.
|
||||
if (unlikely(::std::holds_alternative<char const*>(res))) { // If an error happened (timeout or violation), we return early!
|
||||
error = ::std::get<char const*>(res);
|
||||
goto join;
|
||||
}
|
||||
time_init = ::std::get<Chrono>(res).get_tick();
|
||||
}
|
||||
{ // Performance measurements (with cheap correctness tests)
|
||||
for (unsigned int i = 0; i < nbrepeats; ++i) {
|
||||
sync.master_notify();
|
||||
auto res = sync.master_wait(maxtick_perf);
|
||||
if (unlikely(::std::holds_alternative<char const*>(res))) {
|
||||
error = ::std::get<char const*>(res);
|
||||
goto join;
|
||||
}
|
||||
times[i] = ::std::get<Chrono>(res).get_tick();
|
||||
}
|
||||
::std::nth_element(times, times + posmedian, times + nbrepeats); // Partition times around the median
|
||||
}
|
||||
{ // Correctness check
|
||||
sync.master_notify();
|
||||
auto res = sync.master_wait(maxtick_chck);
|
||||
if (unlikely(::std::holds_alternative<char const*>(res))) {
|
||||
error = ::std::get<char const*>(res);
|
||||
goto join;
|
||||
}
|
||||
time_chck = ::std::get<Chrono>(res).get_tick();
|
||||
}
|
||||
join: { // Joining
|
||||
sync.master_join(); // Join with threads
|
||||
for (unsigned int i = 0; i < nbthreads; ++i)
|
||||
threads[i].join();
|
||||
}
|
||||
return ::std::make_tuple(error, time_init, times[posmedian], time_chck);
|
||||
} catch (...) {
|
||||
for (unsigned int i = 0; i < nbthreads; ++i) // Detach threads to avoid termination due to attached thread going out of scope
|
||||
threads[i].detach();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Program entry point.
|
||||
* @param argc Arguments count
|
||||
* @param argv Arguments values
|
||||
* @return Program return code
|
||||
**/
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
// Parse command line option(s)
|
||||
if (argc < 3) {
|
||||
::std::cout << "Usage: " << (argc > 0 ? argv[0] : "grading") << " <seed> <reference library path> <tested library path>..." << ::std::endl;
|
||||
return 1;
|
||||
}
|
||||
// Get/set/compute run parameters
|
||||
auto const nbworkers = []() {
|
||||
auto res = ::std::thread::hardware_concurrency();
|
||||
if (unlikely(res == 0))
|
||||
res = 16;
|
||||
return static_cast<size_t>(res);
|
||||
}();
|
||||
auto const nbtxperwrk = 200000ul / nbworkers;
|
||||
auto const nbaccounts = 32 * nbworkers;
|
||||
auto const expnbaccounts = 256 * nbworkers;
|
||||
auto const init_balance = 100ul;
|
||||
auto const prob_long = 0.5f;
|
||||
auto const prob_alloc = 0.01f;
|
||||
auto const nbrepeats = 7;
|
||||
auto const seed = static_cast<Seed>(::std::stoul(argv[1]));
|
||||
auto const clk_res = Chrono::get_resolution();
|
||||
auto const slow_factor = 16ul;
|
||||
// Print run parameters
|
||||
::std::cout << "⎧ #worker threads: " << nbworkers << ::std::endl;
|
||||
::std::cout << "⎪ #TX per worker: " << nbtxperwrk << ::std::endl;
|
||||
::std::cout << "⎪ #repetitions: " << nbrepeats << ::std::endl;
|
||||
::std::cout << "⎪ Initial #accounts: " << nbaccounts << ::std::endl;
|
||||
::std::cout << "⎪ Expected #accounts: " << expnbaccounts << ::std::endl;
|
||||
::std::cout << "⎪ Initial balance: " << init_balance << ::std::endl;
|
||||
::std::cout << "⎪ Long TX probability: " << prob_long << ::std::endl;
|
||||
::std::cout << "⎪ Allocation TX prob.: " << prob_alloc << ::std::endl;
|
||||
::std::cout << "⎪ Slow trigger factor: " << slow_factor << ::std::endl;
|
||||
::std::cout << "⎪ Clock resolution: ";
|
||||
if (unlikely(clk_res == Chrono::invalid_tick)) {
|
||||
::std::cout << "<unknown>" << ::std::endl;
|
||||
} else {
|
||||
::std::cout << clk_res << " ns" << ::std::endl;
|
||||
}
|
||||
::std::cout << "⎩ Seed value: " << seed << ::std::endl;
|
||||
// Library evaluations
|
||||
double reference = 0.; // Set to avoid irrelevant '-Wmaybe-uninitialized'
|
||||
auto const pertxdiv = static_cast<double>(nbworkers) * static_cast<double>(nbtxperwrk);
|
||||
auto maxtick_init = Chrono::invalid_tick;
|
||||
auto maxtick_perf = Chrono::invalid_tick;
|
||||
auto maxtick_chck = Chrono::invalid_tick;
|
||||
for (auto i = 2; i < argc; ++i) {
|
||||
::std::cout << "⎧ Evaluating '" << argv[i] << "'" << (maxtick_init == Chrono::invalid_tick ? " (reference)" : "") << "..." << ::std::endl;
|
||||
// Load TM library
|
||||
TransactionalLibrary tl{argv[i]};
|
||||
// Initialize workload (shared memory lifetime bound to workload: created and destroyed at the same time)
|
||||
WorkloadBank bank{tl, nbworkers, nbtxperwrk, nbaccounts, expnbaccounts, init_balance, prob_long, prob_alloc};
|
||||
try {
|
||||
// Actual performance measurements and correctness check
|
||||
auto res = measure(bank, nbworkers, nbrepeats, seed, maxtick_init, maxtick_perf, maxtick_chck);
|
||||
// Check false negative-free correctness
|
||||
auto error = ::std::get<0>(res);
|
||||
if (unlikely(error)) {
|
||||
::std::cout << "⎩ " << error << ::std::endl;
|
||||
return 1;
|
||||
}
|
||||
// Print results
|
||||
auto tick_init = ::std::get<1>(res);
|
||||
auto tick_perf = ::std::get<2>(res);
|
||||
auto tick_chck = ::std::get<3>(res);
|
||||
auto perfdbl = static_cast<double>(tick_perf);
|
||||
::std::cout << "⎪ Total user execution time: " << (perfdbl / 1000000.) << " ms";
|
||||
if (maxtick_init == Chrono::invalid_tick) { // Set reference performance
|
||||
maxtick_init = slow_factor * tick_init;
|
||||
if (unlikely(maxtick_init == Chrono::invalid_tick)) // Bad luck...
|
||||
++maxtick_init;
|
||||
maxtick_perf = slow_factor * tick_perf;
|
||||
if (unlikely(maxtick_perf == Chrono::invalid_tick)) // Bad luck...
|
||||
++maxtick_perf;
|
||||
maxtick_chck = slow_factor * tick_chck;
|
||||
if (unlikely(maxtick_chck == Chrono::invalid_tick)) // Bad luck...
|
||||
++maxtick_chck;
|
||||
reference = perfdbl;
|
||||
} else { // Compare with reference performance
|
||||
::std::cout << " -> " << (reference / perfdbl) << " speedup";
|
||||
}
|
||||
::std::cout << ::std::endl;
|
||||
::std::cout << "⎩ Average TX execution time: " << (perfdbl / pertxdiv) << " ns" << ::std::endl;
|
||||
} catch (::std::exception const& err) { // Special case: cannot unload library with running threads, so print error and quick-exit
|
||||
::std::cerr << "⎪ *** EXCEPTION ***" << ::std::endl;
|
||||
::std::cerr << "⎩ " << err.what() << ::std::endl;
|
||||
::std::quick_exit(2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} catch (::std::exception const& err) {
|
||||
::std::cerr << "⎧ *** EXCEPTION ***" << ::std::endl;
|
||||
::std::cerr << "⎩ " << err.what() << ::std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
620
cs453-ca/CS453-2021-project/grading/transactional.hpp
Normal file
620
cs453-ca/CS453-2021-project/grading/transactional.hpp
Normal file
@@ -0,0 +1,620 @@
|
||||
/**
|
||||
* @file transactional.hpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Transactional memory library management and use.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// External headers
|
||||
extern "C" {
|
||||
#include <dlfcn.h>
|
||||
#include <limits.h>
|
||||
}
|
||||
|
||||
// Internal headers
|
||||
namespace STM {
|
||||
#include <tm.hpp>
|
||||
}
|
||||
#include "common.hpp"
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
namespace Exception {
|
||||
|
||||
/** Exception tree.
|
||||
**/
|
||||
EXCEPTION(Path, Any, "path exception");
|
||||
EXCEPTION(PathResolve, Path, "unable to resolve the given path");
|
||||
EXCEPTION(Module, Any, "transaction library exception");
|
||||
EXCEPTION(ModuleLoading, Module, "unable to load a transaction library");
|
||||
EXCEPTION(ModuleSymbol, Module, "symbol not found in loaded libraries");
|
||||
EXCEPTION(Transaction, Any, "transaction manager exception");
|
||||
EXCEPTION(TransactionAlign, Transaction, "incorrect alignment detected before transactional operation");
|
||||
EXCEPTION(TransactionReadOnly, Transaction, "tried to write/alloc/free using a read-only transaction");
|
||||
EXCEPTION(TransactionCreate, Transaction, "shared memory region creation failed");
|
||||
EXCEPTION(TransactionBegin, Transaction, "transaction begin failed");
|
||||
EXCEPTION(TransactionAlloc, Transaction, "memory allocation failed (insufficient memory)");
|
||||
EXCEPTION(TransactionRetry, Transaction, "transaction aborted and can be retried");
|
||||
EXCEPTION(TransactionNotLastSegment, Transaction, "trying to deallocate the first segment");
|
||||
EXCEPTION(Shared, Any, "operation in shared memory exception");
|
||||
EXCEPTION(SharedAlign, Shared, "address in shared memory is not properly aligned for the specified type");
|
||||
EXCEPTION(SharedOverflow, Shared, "index is past array length");
|
||||
EXCEPTION(SharedDoubleAlloc, Shared, "(probable) double allocation detected before transactional operation");
|
||||
EXCEPTION(SharedDoubleFree, Shared, "double free detected before transactional operation");
|
||||
|
||||
}
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Transactional library management class.
|
||||
**/
|
||||
class TransactionalLibrary final: private NonCopyable {
|
||||
friend class TransactionalMemory;
|
||||
private:
|
||||
/** Function types.
|
||||
**/
|
||||
using FnCreate = decltype(&STM::tm_create);
|
||||
using FnDestroy = decltype(&STM::tm_destroy);
|
||||
using FnStart = decltype(&STM::tm_start);
|
||||
using FnSize = decltype(&STM::tm_size);
|
||||
using FnAlign = decltype(&STM::tm_align);
|
||||
using FnBegin = decltype(&STM::tm_begin);
|
||||
using FnEnd = decltype(&STM::tm_end);
|
||||
using FnRead = decltype(&STM::tm_read);
|
||||
using FnWrite = decltype(&STM::tm_write);
|
||||
using FnAlloc = decltype(&STM::tm_alloc);
|
||||
using FnFree = decltype(&STM::tm_free);
|
||||
private:
|
||||
void* module; // Module opaque handler
|
||||
FnCreate tm_create; // Module's initialization function
|
||||
FnDestroy tm_destroy; // Module's cleanup function
|
||||
FnStart tm_start; // Module's start address query function
|
||||
FnSize tm_size; // Module's size query function
|
||||
FnAlign tm_align; // Module's alignment query function
|
||||
FnBegin tm_begin; // Module's transaction begin function
|
||||
FnEnd tm_end; // Module's transaction end function
|
||||
FnRead tm_read; // Module's shared memory read function
|
||||
FnWrite tm_write; // Module's shared memory write function
|
||||
FnAlloc tm_alloc; // Module's shared memory allocation function
|
||||
FnFree tm_free; // Module's shared memory freeing function
|
||||
private:
|
||||
/** Solve a symbol from its name, and bind it to the given function.
|
||||
* @param name Name of the symbol to resolve
|
||||
* @param func Target function to bind (optional, to use template parameter deduction)
|
||||
**/
|
||||
template<class Signature> auto solve(char const* name) const {
|
||||
auto res = ::dlsym(module, name);
|
||||
if (unlikely(!res))
|
||||
throw Exception::ModuleSymbol{};
|
||||
return *reinterpret_cast<Signature*>(&res);
|
||||
}
|
||||
template<class Signature> void solve(char const* name, Signature& func) const {
|
||||
func = solve<Signature>(name);
|
||||
}
|
||||
public:
|
||||
/** Loader constructor.
|
||||
* @param path Path to the library to load
|
||||
**/
|
||||
TransactionalLibrary(char const* path) {
|
||||
{ // Resolve path and load module
|
||||
char resolved[PATH_MAX];
|
||||
if (unlikely(!realpath(path, resolved)))
|
||||
throw Exception::PathResolve{};
|
||||
module = ::dlopen(resolved, RTLD_NOW | RTLD_LOCAL);
|
||||
if (unlikely(!module))
|
||||
throw Exception::ModuleLoading{};
|
||||
}
|
||||
{ // Bind module's 'tm_*' symbols
|
||||
solve("tm_create", tm_create);
|
||||
solve("tm_destroy", tm_destroy);
|
||||
solve("tm_start", tm_start);
|
||||
solve("tm_size", tm_size);
|
||||
solve("tm_align", tm_align);
|
||||
solve("tm_begin", tm_begin);
|
||||
solve("tm_end", tm_end);
|
||||
solve("tm_read", tm_read);
|
||||
solve("tm_write", tm_write);
|
||||
solve("tm_alloc", tm_alloc);
|
||||
solve("tm_free", tm_free);
|
||||
}
|
||||
}
|
||||
/** Unloader destructor.
|
||||
**/
|
||||
~TransactionalLibrary() noexcept {
|
||||
::dlclose(module); // Close loaded module
|
||||
}
|
||||
};
|
||||
|
||||
/** One shared memory region management class.
|
||||
**/
|
||||
class TransactionalMemory final: private NonCopyable {
|
||||
private:
|
||||
/** Check whether the given alignment is a power of 2
|
||||
**/
|
||||
constexpr static bool is_power_of_two(size_t align) noexcept {
|
||||
return align != 0 && (align & (align - 1)) == 0;
|
||||
}
|
||||
public:
|
||||
/** Opaque shared memory region handle class.
|
||||
**/
|
||||
using Shared = STM::shared_t;
|
||||
/** Transaction class alias.
|
||||
**/
|
||||
using TX = STM::tx_t;
|
||||
private:
|
||||
TransactionalLibrary const& tl; // Bound transactional library
|
||||
Shared shared; // Handle of the shared memory region used
|
||||
void* start_addr; // Shared memory region first segment's start address
|
||||
size_t start_size; // Shared memory region first segment's size (in bytes)
|
||||
size_t alignment; // Shared memory region alignment (in bytes)
|
||||
public:
|
||||
/** Bind constructor.
|
||||
* @param library Transactional library to use
|
||||
* @param align Shared memory region required alignment
|
||||
* @param size Size of the shared memory region to allocate
|
||||
**/
|
||||
TransactionalMemory(TransactionalLibrary const& library, size_t align, size_t size): tl{library}, start_size{size}, alignment{align} {
|
||||
if (unlikely(assert_mode && (!is_power_of_two(align) || size % align != 0)))
|
||||
throw Exception::TransactionAlign{};
|
||||
bounded_run(max_side_time, [&]() {
|
||||
shared = tl.tm_create(size, align);
|
||||
if (unlikely(shared == STM::invalid_shared))
|
||||
throw Exception::TransactionCreate{};
|
||||
start_addr = tl.tm_start(shared);
|
||||
}, "The transactional library takes too long creating the shared memory");
|
||||
}
|
||||
/** Unbind destructor.
|
||||
**/
|
||||
~TransactionalMemory() noexcept {
|
||||
bounded_run(max_side_time, [&]() {
|
||||
tl.tm_destroy(shared);
|
||||
}, "The transactional library takes too long destroying the shared memory");
|
||||
}
|
||||
public:
|
||||
/** [thread-safe] Return the start address of the first shared segment.
|
||||
* @return Address of the first allocated shared region
|
||||
**/
|
||||
auto get_start() const noexcept {
|
||||
return start_addr;
|
||||
}
|
||||
/** [thread-safe] Return the size of the first shared segment.
|
||||
* @return Size in the first allocated shared region (in bytes)
|
||||
**/
|
||||
auto get_size() const noexcept {
|
||||
return start_size;
|
||||
}
|
||||
/** [thread-safe] Get the shared memory region global alignment.
|
||||
* @return Global alignment (in bytes)
|
||||
**/
|
||||
auto get_align() const noexcept {
|
||||
return alignment;
|
||||
}
|
||||
public:
|
||||
/** [thread-safe] Begin a new transaction on the shared memory region.
|
||||
* @param ro Whether the transaction is read-only
|
||||
* @return Opaque transaction ID, 'STM::invalid_tx' on failure
|
||||
**/
|
||||
auto begin(bool ro) const noexcept {
|
||||
return tl.tm_begin(shared, ro);
|
||||
}
|
||||
/** [thread-safe] End the given transaction.
|
||||
* @param tx Opaque transaction ID
|
||||
* @return Whether the whole transaction is a success
|
||||
**/
|
||||
auto end(TX tx) const noexcept {
|
||||
return tl.tm_end(shared, tx);
|
||||
}
|
||||
/** [thread-safe] Read operation in the given transaction, source in the shared region and target in a private region.
|
||||
* @param tx Transaction to use
|
||||
* @param source Source start address
|
||||
* @param size Source/target range
|
||||
* @param target Target start address
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
auto read(TX tx, void const* source, size_t size, void* target) const noexcept {
|
||||
return tl.tm_read(shared, tx, source, size, target);
|
||||
}
|
||||
/** [thread-safe] Write operation in the given transaction, source in a private region and target in the shared region.
|
||||
* @param tx Transaction to use
|
||||
* @param source Source start address
|
||||
* @param size Source/target range
|
||||
* @param target Target start address
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
auto write(TX tx, void const* source, size_t size, void* target) const noexcept {
|
||||
return tl.tm_write(shared, tx, source, size, target);
|
||||
}
|
||||
/** [thread-safe] Memory allocation operation in the given transaction, throw if no memory available.
|
||||
* @param tx Transaction to use
|
||||
* @param size Size to allocate
|
||||
* @param target Target start address
|
||||
* @return Allocation status
|
||||
**/
|
||||
auto alloc(TX tx, size_t size, void** target) const noexcept {
|
||||
return tl.tm_alloc(shared, tx, size, target);
|
||||
}
|
||||
/** [thread-safe] Memory freeing operation in the given transaction.
|
||||
* @param tx Transaction to use
|
||||
* @param target Target start address
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
auto free(TX tx, void* target) const noexcept {
|
||||
return tl.tm_free(shared, tx, target);
|
||||
}
|
||||
};
|
||||
|
||||
/** One transaction over a shared memory region management class.
|
||||
**/
|
||||
class Transaction final: private NonCopyable {
|
||||
public:
|
||||
/** Transaction mode class.
|
||||
**/
|
||||
enum class Mode: bool {
|
||||
read_write = false,
|
||||
read_only = true
|
||||
};
|
||||
private:
|
||||
TransactionalMemory const& tm; // Bound transactional memory
|
||||
STM::tx_t tx; // Opaque transaction handle
|
||||
bool aborted; // Transaction was aborted
|
||||
bool is_ro; // Whether the transaction is read-only (solely for assertion)
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
Transaction(Transaction const&) = delete;
|
||||
Transaction& operator=(Transaction const&) = delete;
|
||||
/** Begin constructor.
|
||||
* @param tm Transactional memory to bind
|
||||
* @param ro Whether the transaction is read-only
|
||||
**/
|
||||
Transaction(TransactionalMemory const& tm, Mode ro): tm{tm}, tx{tm.begin(static_cast<bool>(ro))}, aborted{false}, is_ro{static_cast<bool>(ro)} {
|
||||
if (unlikely(tx == STM::invalid_tx))
|
||||
throw Exception::TransactionBegin{};
|
||||
}
|
||||
/** End destructor.
|
||||
**/
|
||||
~Transaction() noexcept(false) {
|
||||
if (likely(!aborted)) {
|
||||
if (unlikely(!tm.end(tx)))
|
||||
throw Exception::TransactionRetry{};
|
||||
}
|
||||
}
|
||||
public:
|
||||
/** [thread-safe] Return the bound transactional memory instance.
|
||||
* @return Bound transactional memory instance
|
||||
**/
|
||||
auto const& get_tm() const noexcept {
|
||||
return tm;
|
||||
}
|
||||
public:
|
||||
/** [thread-safe] Read operation in the bound transaction, source in the shared region and target in a private region.
|
||||
* @param source Source start address
|
||||
* @param size Source/target range
|
||||
* @param target Target start address
|
||||
**/
|
||||
void read(void const* source, size_t size, void* target) {
|
||||
if (unlikely(!tm.read(tx, source, size, target))) {
|
||||
aborted = true;
|
||||
throw Exception::TransactionRetry{};
|
||||
}
|
||||
}
|
||||
/** [thread-safe] Write operation in the bound transaction, source in a private region and target in the shared region.
|
||||
* @param source Source start address
|
||||
* @param size Source/target range
|
||||
* @param target Target start address
|
||||
**/
|
||||
void write(void const* source, size_t size, void* target) {
|
||||
if (unlikely(assert_mode && is_ro))
|
||||
throw Exception::TransactionReadOnly{};
|
||||
if (unlikely(!tm.write(tx, source, size, target))) {
|
||||
aborted = true;
|
||||
throw Exception::TransactionRetry{};
|
||||
}
|
||||
}
|
||||
/** [thread-safe] Memory allocation operation in the bound transaction, throw if no memory available.
|
||||
* @param size Size to allocate
|
||||
* @return Target start address
|
||||
**/
|
||||
void* alloc(size_t size) {
|
||||
if (unlikely(assert_mode && is_ro))
|
||||
throw Exception::TransactionReadOnly{};
|
||||
void* target;
|
||||
switch (tm.alloc(tx, size, &target)) {
|
||||
case STM::Alloc::success:
|
||||
return target;
|
||||
case STM::Alloc::nomem:
|
||||
throw Exception::TransactionAlloc{};
|
||||
default: // STM::Alloc::abort
|
||||
aborted = true;
|
||||
throw Exception::TransactionRetry{};
|
||||
}
|
||||
}
|
||||
/** [thread-safe] Memory freeing operation in the bound transaction.
|
||||
* @param target Target start address
|
||||
**/
|
||||
void free(void* target) {
|
||||
if (unlikely(assert_mode && is_ro))
|
||||
throw Exception::TransactionReadOnly{};
|
||||
if (unlikely(!tm.free(tx, target))) {
|
||||
aborted = true;
|
||||
throw Exception::TransactionRetry{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Shared read/write helper class.
|
||||
* @param Type Specified type (array)
|
||||
**/
|
||||
template<class Type> class Shared {
|
||||
protected:
|
||||
Transaction& tx; // Bound transaction
|
||||
Type* address; // Address in shared memory
|
||||
public:
|
||||
/** Binding constructor.
|
||||
* @param tx Bound transaction
|
||||
* @param address Address to bind to
|
||||
**/
|
||||
Shared(Transaction& tx, void* address): tx{tx}, address{reinterpret_cast<Type*>(address)} {
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % tx.get_tm().get_align() != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % alignof(Type) != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
}
|
||||
public:
|
||||
/** Get the address in shared memory.
|
||||
* @return Address in shared memory
|
||||
**/
|
||||
auto get() const noexcept {
|
||||
return address;
|
||||
}
|
||||
public:
|
||||
/** Read operation.
|
||||
* @return Private copy of the content at the shared address
|
||||
**/
|
||||
Type read() const {
|
||||
Type res;
|
||||
tx.read(address, sizeof(Type), &res);
|
||||
return res;
|
||||
}
|
||||
operator Type() const {
|
||||
return read();
|
||||
}
|
||||
/** Write operation.
|
||||
* @param source Private content to write at the shared address
|
||||
**/
|
||||
void write(Type const& source) const {
|
||||
tx.write(&source, sizeof(Type), address);
|
||||
}
|
||||
void operator=(Type const& source) const {
|
||||
return write(source);
|
||||
}
|
||||
public:
|
||||
/** Address of the first byte after the entry.
|
||||
* @return First byte after the entry
|
||||
**/
|
||||
void* after() const noexcept {
|
||||
return address + 1;
|
||||
}
|
||||
};
|
||||
template<class Type> class Shared<Type*> {
|
||||
protected:
|
||||
Transaction& tx; // Bound transaction
|
||||
Type** address; // Address in shared memory
|
||||
public:
|
||||
/** Binding constructor.
|
||||
* @param tx Bound transaction
|
||||
* @param address Address to bind to
|
||||
**/
|
||||
Shared(Transaction& tx, void* address): tx{tx}, address{reinterpret_cast<Type**>(address)} {
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % tx.get_tm().get_align() != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % alignof(Type*) != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
}
|
||||
public:
|
||||
/** Get the address in shared memory.
|
||||
* @return Address in shared memory
|
||||
**/
|
||||
auto get() const noexcept {
|
||||
return address;
|
||||
}
|
||||
public:
|
||||
/** Read operation.
|
||||
* @return Private copy of the content at the shared address
|
||||
**/
|
||||
Type* read() const {
|
||||
Type* res;
|
||||
tx.read(address, sizeof(Type*), &res);
|
||||
return res;
|
||||
}
|
||||
operator Type*() const {
|
||||
return read();
|
||||
}
|
||||
/** Write operation.
|
||||
* @param source Private content to write at the shared address
|
||||
**/
|
||||
void write(Type* source) const {
|
||||
tx.write(&source, sizeof(Type*), address);
|
||||
}
|
||||
void operator=(Type* source) const {
|
||||
return write(source);
|
||||
}
|
||||
/** Allocate and write operation.
|
||||
* @param size Size to allocate (defaults to size of the underlying class)
|
||||
* @return Private copy of the just-written content at the shared address
|
||||
**/
|
||||
Type* alloc(size_t size = 0) const {
|
||||
if (unlikely(assert_mode && read() != nullptr))
|
||||
throw Exception::SharedDoubleAlloc{};
|
||||
auto addr = tx.alloc(size > 0 ? size: sizeof(Type));
|
||||
write(reinterpret_cast<Type*>(addr));
|
||||
return reinterpret_cast<Type*>(addr);
|
||||
}
|
||||
/** Free and write operation.
|
||||
**/
|
||||
void free() const {
|
||||
if (unlikely(assert_mode && read() == nullptr))
|
||||
throw Exception::SharedDoubleFree{};
|
||||
tx.free(read());
|
||||
write(nullptr);
|
||||
}
|
||||
public:
|
||||
/** Address of the first byte after the entry.
|
||||
* @return First byte after the entry
|
||||
**/
|
||||
void* after() const noexcept {
|
||||
return address + 1;
|
||||
}
|
||||
};
|
||||
template<class Type> class Shared<Type[]> {
|
||||
protected:
|
||||
Transaction& tx; // Bound transaction
|
||||
Type* address; // Address of the first element in shared memory
|
||||
public:
|
||||
/** Binding constructor.
|
||||
* @param tx Bound transaction
|
||||
* @param address Address to bind to
|
||||
**/
|
||||
Shared(Transaction& tx, void* address): tx{tx}, address{reinterpret_cast<Type*>(address)} {
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % tx.get_tm().get_align() != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % alignof(Type) != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
}
|
||||
public:
|
||||
/** Get the address in shared memory.
|
||||
* @return Address in shared memory
|
||||
**/
|
||||
auto get() const noexcept {
|
||||
return address;
|
||||
}
|
||||
public:
|
||||
/** Read operation.
|
||||
* @param index Index to read
|
||||
* @return Private copy of the content at the shared address
|
||||
**/
|
||||
Type read(size_t index) const {
|
||||
Type res;
|
||||
tx.read(address + index, sizeof(Type), &res);
|
||||
return res;
|
||||
}
|
||||
/** Write operation.
|
||||
* @param index Index to write
|
||||
* @param source Private content to write at the shared address
|
||||
**/
|
||||
void write(size_t index, Type const& source) const {
|
||||
tx.write(tx, &source, sizeof(Type), address + index);
|
||||
}
|
||||
public:
|
||||
/** Reference a cell.
|
||||
* @param index Cell to reference
|
||||
* @return Shared on that cell
|
||||
**/
|
||||
Shared<Type> operator[](size_t index) const {
|
||||
return Shared<Type>{tx, address + index};
|
||||
}
|
||||
/** Address of the first byte after the entry.
|
||||
* @param length Length of the array
|
||||
* @return First byte after the entry
|
||||
**/
|
||||
void* after(size_t length) const noexcept {
|
||||
return address + length;
|
||||
}
|
||||
};
|
||||
template<class Type, size_t n> class Shared<Type[n]> {
|
||||
protected:
|
||||
Transaction& tx; // Bound transaction
|
||||
Type* address; // Address of the first element in shared memory
|
||||
public:
|
||||
/** Binding constructor.
|
||||
* @param tx Bound transaction
|
||||
* @param address Address to bind to
|
||||
**/
|
||||
Shared(Transaction& tx, void* address): tx{tx}, address{reinterpret_cast<Type*>(address)} {
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % tx.get_tm().get_align() != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
if (unlikely(assert_mode && reinterpret_cast<uintptr_t>(address) % alignof(Type) != 0))
|
||||
throw Exception::SharedAlign{};
|
||||
}
|
||||
public:
|
||||
/** Get the address in shared memory.
|
||||
* @return Address in shared memory
|
||||
**/
|
||||
auto get() const noexcept {
|
||||
return address;
|
||||
}
|
||||
public:
|
||||
/** Read operation.
|
||||
* @param index Index to read
|
||||
* @return Private copy of the content at the shared address
|
||||
**/
|
||||
Type read(size_t index) const {
|
||||
if (unlikely(assert_mode && index >= n))
|
||||
throw Exception::SharedOverflow{};
|
||||
Type res;
|
||||
tx.read(address + index, sizeof(Type), &res);
|
||||
return res;
|
||||
}
|
||||
/** Write operation.
|
||||
* @param index Index to write
|
||||
* @param source Private content to write at the shared address
|
||||
**/
|
||||
void write(size_t index, Type const& source) const {
|
||||
if (unlikely(assert_mode && index >= n))
|
||||
throw Exception::SharedOverflow{};
|
||||
tx.write(tx, &source, sizeof(Type), address + index);
|
||||
}
|
||||
public:
|
||||
/** Reference a cell.
|
||||
* @param index Cell to reference
|
||||
* @return Shared on that cell
|
||||
**/
|
||||
Shared<Type> operator[](size_t index) const {
|
||||
if (unlikely(assert_mode && index >= n))
|
||||
throw Exception::SharedOverflow{};
|
||||
return Shared<Type>{tx, address + index};
|
||||
}
|
||||
/** Address of the first byte after the array.
|
||||
* @return First byte after the array
|
||||
**/
|
||||
void* after() const noexcept {
|
||||
return address + n;
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Repeat a given transaction until it commits.
|
||||
* @param tm Transactional memory
|
||||
* @param mode Transactional mode
|
||||
* @param func Transaction closure (Transaction& -> ...)
|
||||
* @return Returned value (or void) when the transaction committed
|
||||
**/
|
||||
template<class Func> static auto transactional(TransactionalMemory const& tm, Transaction::Mode mode, Func&& func) {
|
||||
do {
|
||||
try {
|
||||
Transaction tx{tm, mode};
|
||||
return func(tx);
|
||||
} catch (Exception::TransactionRetry const&) {
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
}
|
389
cs453-ca/CS453-2021-project/grading/workload.hpp
Normal file
389
cs453-ca/CS453-2021-project/grading/workload.hpp
Normal file
@@ -0,0 +1,389 @@
|
||||
/**
|
||||
* @file workload.hpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Workload base class and derived workload(s) implementations.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// External headers
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
// Internal headers
|
||||
#include "common.hpp"
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Worker unique ID type.
|
||||
**/
|
||||
using Uid = uint_fast32_t;
|
||||
|
||||
/** Seed type.
|
||||
**/
|
||||
using Seed = uint_fast32_t;
|
||||
|
||||
/** Workload base class.
|
||||
**/
|
||||
class Workload {
|
||||
protected:
|
||||
TransactionalLibrary const& tl; // Associated transactional library
|
||||
TransactionalMemory tm; // Built transactional memory to use
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
Workload(Workload const&) = delete;
|
||||
Workload& operator=(Workload const&) = delete;
|
||||
/** Transactional memory constructor.
|
||||
* @param library Transactional library to use
|
||||
* @param align Shared memory region required alignment
|
||||
* @param size Size of the shared memory region to allocate
|
||||
**/
|
||||
Workload(TransactionalLibrary const& library, size_t align, size_t size): tl{library}, tm{tl, align, size} {}
|
||||
/** Virtual destructor.
|
||||
**/
|
||||
virtual ~Workload() {};
|
||||
public:
|
||||
/** Shared memory (re)initialization.
|
||||
* @return Constant null-terminated error message, 'nullptr' for none
|
||||
**/
|
||||
virtual char const* init() const = 0;
|
||||
/** [thread-safe] Worker's full run.
|
||||
* @param Unique ID (between 0 to n-1)
|
||||
* @param Seed to use
|
||||
* @return Constant null-terminated error message, 'nullptr' for none
|
||||
**/
|
||||
virtual char const* run(Uid, Seed) const = 0;
|
||||
/** [thread-safe] Worker's false negative-free check.
|
||||
* @param Unique ID (between 0 to n-1)
|
||||
* @param Seed to use
|
||||
* @return Constant null-terminated error message, 'nullptr' for none
|
||||
**/
|
||||
virtual char const* check(Uid, Seed) const = 0;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Bank workload class.
|
||||
**/
|
||||
class WorkloadBank final: public Workload {
|
||||
public:
|
||||
/** Account balance class alias.
|
||||
**/
|
||||
using Balance = intptr_t;
|
||||
static_assert(sizeof(Balance) >= sizeof(void*), "Balance class is too small");
|
||||
private:
|
||||
/** Shared segment of accounts class.
|
||||
**/
|
||||
class AccountSegment final {
|
||||
private:
|
||||
/** Dummy structure for size and alignment retrieval.
|
||||
**/
|
||||
struct Dummy {
|
||||
size_t dummy0;
|
||||
void* dummy1;
|
||||
Balance dummy2;
|
||||
Balance dummy3[];
|
||||
};
|
||||
public:
|
||||
/** Get the segment size for a given number of accounts.
|
||||
* @param nbaccounts Number of accounts per segment
|
||||
* @return Segment size (in bytes)
|
||||
**/
|
||||
constexpr static auto size(size_t nbaccounts) noexcept {
|
||||
return sizeof(Dummy) + nbaccounts * sizeof(Balance);
|
||||
}
|
||||
/** Get the segment alignment for a given number of accounts.
|
||||
* @return Segment size (in bytes)
|
||||
**/
|
||||
constexpr static auto align() noexcept {
|
||||
return alignof(Dummy);
|
||||
}
|
||||
public:
|
||||
Shared<size_t> count; // Number of allocated accounts in this segment
|
||||
Shared<AccountSegment*> next; // Next allocated segment
|
||||
Shared<Balance> parity; // Segment balance correction for when deleting an account
|
||||
Shared<Balance[]> accounts; // Amount of money on the accounts (undefined if not allocated)
|
||||
public:
|
||||
/** Deleted copy constructor/assignment.
|
||||
**/
|
||||
AccountSegment(AccountSegment const&) = delete;
|
||||
AccountSegment& operator=(AccountSegment const&) = delete;
|
||||
/** Binding constructor.
|
||||
* @param tx Associated pending transaction
|
||||
* @param address Block base address
|
||||
**/
|
||||
AccountSegment(Transaction& tx, void* address): count{tx, address}, next{tx, count.after()}, parity{tx, next.after()}, accounts{tx, parity.after()} {}
|
||||
};
|
||||
private:
|
||||
size_t nbworkers; // Number of concurrent workers
|
||||
size_t nbtxperwrk; // Number of transactions per worker
|
||||
size_t nbaccounts; // Initial number of accounts and number of accounts per segment
|
||||
size_t expnbaccounts; // Expected total number of accounts
|
||||
Balance init_balance; // Initial account balance
|
||||
float prob_long; // Probability of running a long, read-only control transaction
|
||||
float prob_alloc; // Probability of running an allocation/deallocation transaction, knowing a long transaction won't run
|
||||
Barrier barrier; // Barrier for thread synchronization during 'check'
|
||||
public:
|
||||
/** Bank workload constructor.
|
||||
* @param library Transactional library to use
|
||||
* @param nbworkers Total number of concurrent threads (for both 'run' and 'check')
|
||||
* @param nbtxperwrk Number of transactions per worker
|
||||
* @param nbaccounts Initial number of accounts and number of accounts per segment
|
||||
* @param expnbaccounts Initial number of accounts and number of accounts per segment
|
||||
* @param init_balance Initial account balance
|
||||
* @param prob_long Probability of running a long, read-only control transaction
|
||||
* @param prob_alloc Probability of running an allocation/deallocation transaction, knowing a long transaction won't run
|
||||
**/
|
||||
WorkloadBank(TransactionalLibrary const& library, size_t nbworkers, size_t nbtxperwrk, size_t nbaccounts, size_t expnbaccounts, Balance init_balance, float prob_long, float prob_alloc): Workload{library, AccountSegment::align(), AccountSegment::size(nbaccounts)}, nbworkers{nbworkers}, nbtxperwrk{nbtxperwrk}, nbaccounts{nbaccounts}, expnbaccounts{expnbaccounts}, init_balance{init_balance}, prob_long{prob_long}, prob_alloc{prob_alloc}, barrier{nbworkers} {}
|
||||
private:
|
||||
/** Long read-only transaction, summing the balance of each account.
|
||||
* @param count Loosely-updated number of accounts
|
||||
* @return Whether no inconsistency has been found
|
||||
**/
|
||||
bool long_tx(size_t& nbaccounts) const {
|
||||
return transactional(tm, Transaction::Mode::read_only, [&](Transaction& tx) {
|
||||
auto count = 0ul; // Total number of accounts seen.
|
||||
auto sum = Balance{0}; // Total balance on all seen accounts + parity ammount.
|
||||
auto start = tm.get_start(); // The list of accounts starts at the first word of the shared memory region.
|
||||
while (start) {
|
||||
AccountSegment segment{tx, start}; // We interpret the memory as a segment/array of accounts.
|
||||
decltype(count) segment_count = segment.count;
|
||||
count += segment_count; // And accumulate the total number of accounts.
|
||||
sum += segment.parity; // We also sum the money that results from the destruction of accounts.
|
||||
for (decltype(count) i = 0; i < segment_count; ++i) {
|
||||
Balance local = segment.accounts[i];
|
||||
if (unlikely(local < 0)) // If one account has a negative balance, there's a consistency issue.
|
||||
return false;
|
||||
sum += local;
|
||||
}
|
||||
start = segment.next; // Accounts are stored in linked segments, we move to the next one.
|
||||
}
|
||||
nbaccounts = count;
|
||||
return sum == static_cast<Balance>(init_balance * count); // Consistency check: no money should ever be destroyed or created out of thin air.
|
||||
});
|
||||
}
|
||||
/** Account (de)allocation transaction, adding accounts with initial balance or removing them.
|
||||
* @param trigger Trigger level that will decide whether to allocate or deallocate
|
||||
**/
|
||||
void alloc_tx(size_t trigger) const {
|
||||
return transactional(tm, Transaction::Mode::read_write, [&](Transaction& tx) {
|
||||
auto count = 0ul; // Total number of accounts seen.
|
||||
void* prev = nullptr;
|
||||
auto start = tm.get_start();
|
||||
while (true) {
|
||||
AccountSegment segment{tx, start};
|
||||
decltype(count) segment_count = segment.count;
|
||||
count += segment_count;
|
||||
decltype(start) segment_next = segment.next;
|
||||
if (!segment_next) { // Currently at the last segment
|
||||
if (count > trigger && likely(count > 2)) { // If we have seen "too many" accounts, we will destroy one.
|
||||
--segment_count; // Let's remove the last account from the last segment.
|
||||
auto new_parity = segment.parity.read() + segment.accounts[segment_count] - init_balance; // We remove 1x the initial balance but don't break parity.
|
||||
if (segment_count > 0) { // Just remove one account from the (last) segment without deallocating memory.
|
||||
segment.count = segment_count;
|
||||
segment.parity = new_parity;
|
||||
} else { // If there's no one in the last segment anymore, we deallocate it.
|
||||
if (unlikely(assert_mode && prev == nullptr))
|
||||
throw Exception::TransactionNotLastSegment{};
|
||||
AccountSegment prev_segment{tx, prev};
|
||||
prev_segment.next.free();
|
||||
prev_segment.parity = prev_segment.parity.read() + new_parity;
|
||||
}
|
||||
} else { // If we don't destroy any account, then let's create a new one.
|
||||
if (segment_count < nbaccounts) { // If there's room in the last segment, then let's create the account in it without allocating memory.
|
||||
segment.accounts[segment_count] = init_balance;
|
||||
segment.count = segment_count + 1;
|
||||
} else { // Otherwise, we really need to allocate memory for the new account.
|
||||
AccountSegment next_segment{tx, segment.next.alloc(AccountSegment::size(nbaccounts))};
|
||||
next_segment.count = 1;
|
||||
next_segment.accounts[0] = init_balance;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
prev = start;
|
||||
start = segment_next;
|
||||
}
|
||||
});
|
||||
}
|
||||
/** Short read-write transaction, transferring one unit from an account to an account (potentially the same).
|
||||
* @param send_id Index of the sender account
|
||||
* @param recv_id Index of the receiver account (potentially same as source)
|
||||
* @return Whether the parameters were satisfying and the transaction committed on useful work
|
||||
**/
|
||||
bool short_tx(size_t send_id, size_t recv_id) const {
|
||||
return transactional(tm, Transaction::Mode::read_write, [&](Transaction& tx) {
|
||||
void* send_ptr = nullptr;
|
||||
void* recv_ptr = nullptr;
|
||||
|
||||
// Get the account pointers in shared memory
|
||||
auto start = tm.get_start();
|
||||
while (true) {
|
||||
AccountSegment segment{tx, start};
|
||||
size_t segment_count = segment.count;
|
||||
if (!send_ptr) {
|
||||
if (send_id < segment_count) {
|
||||
send_ptr = segment.accounts[send_id].get();
|
||||
if (recv_ptr)
|
||||
break;
|
||||
} else {
|
||||
send_id -= segment_count;
|
||||
}
|
||||
}
|
||||
if (!recv_ptr) {
|
||||
if (recv_id < segment_count) {
|
||||
recv_ptr = segment.accounts[recv_id].get();
|
||||
if (send_ptr)
|
||||
break;
|
||||
} else {
|
||||
recv_id -= segment_count;
|
||||
}
|
||||
}
|
||||
start = segment.next;
|
||||
if (!start) // Current segment is the last segment
|
||||
return false; // At least one account does not exist => do nothing
|
||||
}
|
||||
|
||||
// Transfer the money if enough fund
|
||||
Shared<Balance> sender{tx, send_ptr}; // Shared is a template that overloads copy to use tm_read/tm_write.
|
||||
Shared<Balance> recver{tx, recv_ptr};
|
||||
auto send_val = sender.read();
|
||||
if (send_val > 0) {
|
||||
sender = send_val - 1;
|
||||
recver = recver.read() + 1;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* Initialize the first segment of accounts and check the initial ballance (2 transactions).
|
||||
**/
|
||||
virtual char const* init() const {
|
||||
transactional(tm, Transaction::Mode::read_write, [&](Transaction& tx) {
|
||||
AccountSegment segment{tx, tm.get_start()};
|
||||
segment.count = nbaccounts;
|
||||
for (size_t i = 0; i < nbaccounts; ++i)
|
||||
segment.accounts[i] = init_balance;
|
||||
});
|
||||
auto correct = transactional(tm, Transaction::Mode::read_only, [&](Transaction& tx) {
|
||||
AccountSegment segment{tx, tm.get_start()};
|
||||
return segment.accounts[0] == init_balance;
|
||||
});
|
||||
if (unlikely(!correct))
|
||||
return "Violated consistency (check that committed writes in shared memory get visible to the following transactions' reads)";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run nbtxperwrk random transactions until completion.
|
||||
* @param seed Randomness source
|
||||
**/
|
||||
virtual char const* run(Uid uid [[gnu::unused]], Seed seed) const {
|
||||
::std::minstd_rand engine{seed};
|
||||
::std::bernoulli_distribution long_dist{prob_long};
|
||||
::std::bernoulli_distribution alloc_dist{prob_alloc};
|
||||
::std::gamma_distribution<float> alloc_trigger(expnbaccounts, 1);
|
||||
size_t count = nbaccounts;
|
||||
for (size_t cntr = 0; cntr < nbtxperwrk; ++cntr) {
|
||||
if (long_dist(engine)) { // We roll a dice and, if "lucky", run a long transaction.
|
||||
if (unlikely(!long_tx(count))) // If it fails, then we return an error message.
|
||||
return "Violated isolation or atomicity";
|
||||
} else if (alloc_dist(engine)) { // Let's roll a dice again to trigger an allocation transaction.
|
||||
alloc_tx(alloc_trigger(engine));
|
||||
} else { // No luck with previous rolls, let's just run a short transaction.
|
||||
::std::uniform_int_distribution<size_t> account{0, count - 1};
|
||||
while (unlikely(!short_tx(account(engine), account(engine))));
|
||||
}
|
||||
}
|
||||
{ // Last long transaction
|
||||
size_t dummy;
|
||||
if (!long_tx(dummy))
|
||||
return "Violated isolation or atomicity";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
/**
|
||||
* Test in which we check that multiple concurrent transactions can decrease a counter in a sequential manner.
|
||||
* @param uid Id of the thread to run the check
|
||||
**/
|
||||
virtual char const* check(Uid uid, Seed seed [[gnu::unused]]) const {
|
||||
constexpr size_t nbtxperwrk = 100;
|
||||
|
||||
barrier.sync();
|
||||
if (uid == 0) { // Only the first thread initializes the shared memory.
|
||||
// We first write the initial value,
|
||||
auto init_counter = nbtxperwrk * nbworkers;
|
||||
transactional(tm, Transaction::Mode::read_write, [&](Transaction& tx) {
|
||||
Shared<size_t> counter{tx, tm.get_start()};
|
||||
counter = init_counter;
|
||||
});
|
||||
|
||||
// And check in another transaction that it was written correctly.
|
||||
auto correct = transactional(tm, Transaction::Mode::read_only, [&](Transaction& tx) {
|
||||
Shared<size_t> counter{tx, tm.get_start()};
|
||||
return counter == init_counter;
|
||||
});
|
||||
if (unlikely(!correct)) {
|
||||
barrier.sync();
|
||||
barrier.sync();
|
||||
return "Violated consistency during initialization";
|
||||
}
|
||||
}
|
||||
|
||||
// In each thread,
|
||||
barrier.sync();
|
||||
for (size_t i = 0; i < nbtxperwrk; ++i) {
|
||||
|
||||
// We first fetch the last value of the counter,
|
||||
auto last = transactional(tm, Transaction::Mode::read_only, [&](Transaction& tx) {
|
||||
Shared<size_t> counter{tx, tm.get_start()};
|
||||
return counter.read();
|
||||
});
|
||||
|
||||
// And then we decrease the value of the counter after checking that it didn't increase since the last read.
|
||||
auto correct = transactional(tm, Transaction::Mode::read_write, [&](Transaction& tx) {
|
||||
Shared<size_t> counter{tx, tm.get_start()};
|
||||
auto value = counter.read();
|
||||
if (unlikely(value > last))
|
||||
return false;
|
||||
counter = value - 1;
|
||||
return true;
|
||||
});
|
||||
if (unlikely(!correct)) {
|
||||
barrier.sync();
|
||||
return "Violated consistency, isolation or atomicity";
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, a last transaction runs in the first thread to check that the counter reached 0 (i.e., each transaction decreased it by 1.).
|
||||
barrier.sync();
|
||||
if (uid == 0) {
|
||||
auto correct = transactional(tm, Transaction::Mode::read_only, [&](Transaction& tx) {
|
||||
Shared<size_t> counter{tx, tm.get_start()};
|
||||
return counter == 0;
|
||||
});
|
||||
if (unlikely(!correct))
|
||||
return "Violated consistency";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
61
cs453-ca/CS453-2021-project/include/tm.h
Normal file
61
cs453-ca/CS453-2021-project/include/tm.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file tm.h
|
||||
* @author Sébastien ROUAULT <sebastien.rouault@epfl.ch>
|
||||
* @author Antoine MURAT <antoine.murat@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2021 Sébastien ROUAULT.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Interface declaration for the transaction manager to use (C version).
|
||||
* YOU SHOULD NOT MODIFY THIS FILE.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
typedef void* shared_t; // The type of a shared memory region
|
||||
static shared_t const invalid_shared = NULL; // Invalid shared memory region
|
||||
|
||||
// Note: a uintptr_t is an unsigned integer that is big enough to store an
|
||||
// address. Said differently, you can either use an integer to identify
|
||||
// transactions, or an address (e.g., if you created an associated data
|
||||
// structure).
|
||||
typedef uintptr_t tx_t; // The type of a transaction identifier
|
||||
static tx_t const invalid_tx = ~((tx_t) 0); // Invalid transaction constant
|
||||
|
||||
typedef int alloc_t;
|
||||
static alloc_t const success_alloc = 0; // Allocation successful and the TX can continue
|
||||
static alloc_t const abort_alloc = 1; // TX was aborted and could be retried
|
||||
static alloc_t const nomem_alloc = 2; // Memory allocation failed but TX was not aborted
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
shared_t tm_create(size_t, size_t);
|
||||
void tm_destroy(shared_t);
|
||||
void* tm_start(shared_t);
|
||||
size_t tm_size(shared_t);
|
||||
size_t tm_align(shared_t);
|
||||
tx_t tm_begin(shared_t, bool);
|
||||
bool tm_end(shared_t, tx_t);
|
||||
bool tm_read(shared_t, tx_t, void const*, size_t, void*);
|
||||
bool tm_write(shared_t, tx_t, void const*, size_t, void*);
|
||||
alloc_t tm_alloc(shared_t, tx_t, size_t, void**);
|
||||
bool tm_free(shared_t, tx_t, void*);
|
63
cs453-ca/CS453-2021-project/include/tm.hpp
Normal file
63
cs453-ca/CS453-2021-project/include/tm.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @file tm.hpp
|
||||
* @author Sébastien ROUAULT <sebastien.rouault@epfl.ch>
|
||||
* @author Antoine MURAT <antoine.murat@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2021 Sébastien ROUAULT.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Interface declaration for the transaction manager to use (C++ version).
|
||||
* YOU SHOULD NOT MODIFY THIS FILE.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
using shared_t = void*; // The type of a shared memory region
|
||||
constexpr static shared_t invalid_shared = nullptr; // Invalid shared memory region
|
||||
|
||||
// Note: a uintptr_t is an unsigned integer that is big enough to store an
|
||||
// address. Said differently, you can either use an integer to identify
|
||||
// transactions, or an address (e.g., if you created an associated data
|
||||
// structure).
|
||||
using tx_t = uintptr_t; // The type of a transaction identifier
|
||||
constexpr static tx_t invalid_tx = ~(tx_t(0)); // Invalid transaction constant
|
||||
|
||||
enum class Alloc: int {
|
||||
success = 0, // Allocation successful and the TX can continue
|
||||
abort = 1, // TX was aborted and could be retried
|
||||
nomem = 2 // Memory allocation failed but TX was not aborted
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
extern "C" {
|
||||
shared_t tm_create(size_t, size_t) noexcept;
|
||||
void tm_destroy(shared_t) noexcept;
|
||||
void* tm_start(shared_t) noexcept;
|
||||
size_t tm_size(shared_t) noexcept;
|
||||
size_t tm_align(shared_t) noexcept;
|
||||
tx_t tm_begin(shared_t, bool) noexcept;
|
||||
bool tm_end(shared_t, tx_t) noexcept;
|
||||
bool tm_read(shared_t, tx_t, void const*, size_t, void*) noexcept;
|
||||
bool tm_write(shared_t, tx_t, void const*, size_t, void*) noexcept;
|
||||
Alloc tm_alloc(shared_t, tx_t, size_t, void**) noexcept;
|
||||
bool tm_free(shared_t, tx_t, void*) noexcept;
|
||||
}
|
48
cs453-ca/CS453-2021-project/playground/Makefile
Normal file
48
cs453-ca/CS453-2021-project/playground/Makefile
Normal file
@@ -0,0 +1,48 @@
|
||||
BIN := $(notdir $(lastword $(abspath .)))
|
||||
|
||||
EXT_H := h
|
||||
EXT_HPP := h hh hpp hxx h++
|
||||
EXT_C := c
|
||||
EXT_CXX := C cc cpp cxx c++
|
||||
|
||||
INCLUDE_DIR := .
|
||||
SOURCE_DIR := .
|
||||
|
||||
WILD_EXT = $(strip $(foreach EXT,$($(1)),$(wildcard $(2)/*.$(EXT))))
|
||||
|
||||
HDRS_C := $(call WILD_EXT,EXT_H,$(INCLUDE_DIR))
|
||||
HDRS_CXX := $(call WILD_EXT,EXT_HPP,$(INCLUDE_DIR))
|
||||
SRCS_C := $(call WILD_EXT,EXT_C,$(SOURCE_DIR))
|
||||
SRCS_CXX := $(call WILD_EXT,EXT_CXX,$(SOURCE_DIR))
|
||||
OBJS := $(SRCS_C:%=%.o) $(SRCS_CXX:%=%.o)
|
||||
|
||||
CC := $(CC)
|
||||
CCFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c11 -fPIC -I$(INCLUDE_DIR)
|
||||
CXX := $(CXX)
|
||||
CXXFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c++17 -fPIC -I$(INCLUDE_DIR)
|
||||
LD := $(if $(SRCS_CXX),$(CXX),$(CC))
|
||||
LDFLAGS :=
|
||||
LDLIBS := -lpthread
|
||||
|
||||
.PHONY: build run clean
|
||||
|
||||
build: $(BIN)
|
||||
run: $(BIN)
|
||||
./$(BIN)
|
||||
clean:
|
||||
$(RM) $(OBJS) $(BIN)
|
||||
|
||||
define BUILD_C
|
||||
%.$(1).o: %.$(1) $$(HDRS_C) Makefile
|
||||
$$(CC) $$(CCFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_C),$(eval $(call BUILD_C,$(EXT))))
|
||||
|
||||
define BUILD_CXX
|
||||
%.$(1).o: %.$(1) $$(HDRS_CXX) Makefile
|
||||
$$(CXX) $$(CXXFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_CXX),$(eval $(call BUILD_CXX,$(EXT))))
|
||||
|
||||
$(BIN): $(OBJS) Makefile
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
|
77
cs453-ca/CS453-2021-project/playground/entrypoint.cpp
Normal file
77
cs453-ca/CS453-2021-project/playground/entrypoint.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @file entrypoint.cpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* "Entry point" source file, implementing the playground function 'entry_point' and the lock.
|
||||
**/
|
||||
|
||||
// External headers
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
|
||||
// Internal headers
|
||||
#include "entrypoint.hpp"
|
||||
#include "runner.hpp"
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Lock implementation, that you have to complete
|
||||
|
||||
// NOTE: You may want to add data member(s) in 'class Lock' at entrypoint.hpp:30
|
||||
|
||||
/** Lock default constructor.
|
||||
**/
|
||||
Lock::Lock() {
|
||||
// ...
|
||||
}
|
||||
|
||||
/** Lock destructor.
|
||||
**/
|
||||
Lock::~Lock() {
|
||||
// ...
|
||||
}
|
||||
|
||||
/** [thread-safe] Acquire the lock, block if it is already acquired.
|
||||
**/
|
||||
void Lock::lock() {
|
||||
// ...
|
||||
}
|
||||
|
||||
/** [thread-safe] Release the lock, assuming it is indeed held by the caller.
|
||||
**/
|
||||
void Lock::unlock() {
|
||||
// ...
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Thread accessing the shared memory (a mere shared counter in this program)
|
||||
|
||||
/** Thread entry point.
|
||||
* @param nb Total number of threads
|
||||
* @param id This thread ID (from 0 to nb-1 included)
|
||||
* @param lock Lock to use to protect the shared memory (read & written by 'shared_access')
|
||||
**/
|
||||
void entry_point(size_t nb, size_t id, Lock& lock) {
|
||||
::printf("Hello from thread %lu/%lu\n", id, nb);
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
::std::lock_guard<Lock> guard{lock}; // Lock is acquired here
|
||||
::shared_access();
|
||||
// Lock is automatically released here (thanks to 'lock_guard', upon leaving the scope)
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/playground/entrypoint.hpp
Normal file
48
cs453-ca/CS453-2021-project/playground/entrypoint.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file entrypoint.hpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Interface for the "entry point" source file.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** Your lock class.
|
||||
**/
|
||||
class Lock final {
|
||||
public:
|
||||
/** Deleted copy/move constructor/assignment.
|
||||
**/
|
||||
Lock(Lock const&) = delete;
|
||||
Lock& operator=(Lock const&) = delete;
|
||||
// NOTE: Actually, one could argue it makes sense to implement move,
|
||||
// but we don't care about this feature in our simple playground
|
||||
public:
|
||||
Lock();
|
||||
~Lock();
|
||||
public:
|
||||
void lock();
|
||||
void unlock();
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
void entry_point(size_t, size_t, Lock&);
|
85
cs453-ca/CS453-2021-project/playground/runner.cpp
Normal file
85
cs453-ca/CS453-2021-project/playground/runner.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @file runner.cpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Trivial program that call a function in several threads.
|
||||
**/
|
||||
|
||||
// External headers
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
// Internal headers
|
||||
#include "entrypoint.hpp"
|
||||
#include "runner.hpp"
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Shared memory, access function and consistency check
|
||||
|
||||
static int counter = 0;
|
||||
static ::std::atomic<int> check_counter{0};
|
||||
|
||||
/** Performs some operations on some shared memory.
|
||||
**/
|
||||
void shared_access() {
|
||||
++counter;
|
||||
check_counter.fetch_add(1, ::std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/** (Empirically) checks that concurrent operations did not break consistency, warn accordingly.
|
||||
**/
|
||||
static void shared_check() {
|
||||
auto calls = check_counter.load(::std::memory_order_relaxed);
|
||||
if (counter == calls) {
|
||||
::std::cout << "** No inconsistency detected (" << counter << " == " << calls << ") **" << ::std::endl;
|
||||
} else {
|
||||
::std::cout << "** Inconsistency detected (" << counter << " != " << calls << ") **" << ::std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Lock + thread launches and management
|
||||
|
||||
/** Program entry point.
|
||||
* @param argc Arguments count
|
||||
* @param argv Arguments values
|
||||
* @return Program return code
|
||||
**/
|
||||
int main(int, char**) {
|
||||
auto const nbworkers = []() {
|
||||
auto res = ::std::thread::hardware_concurrency();
|
||||
if (res == 0) {
|
||||
::std::cout << "WARNING: unable to query '::std::thread::hardware_concurrency()', falling back to 4 threads" << ::std::endl;
|
||||
res = 4;
|
||||
}
|
||||
return static_cast<size_t>(res);
|
||||
}();
|
||||
Lock lock;
|
||||
::std::thread threads[nbworkers];
|
||||
for (size_t i = 0; i < nbworkers; ++i) {
|
||||
threads[i] = ::std::thread{[&](size_t i) {
|
||||
entry_point(nbworkers, i, lock);
|
||||
}, i};
|
||||
}
|
||||
for (auto&& thread: threads)
|
||||
thread.join();
|
||||
shared_check();
|
||||
return 0;
|
||||
}
|
28
cs453-ca/CS453-2021-project/playground/runner.hpp
Normal file
28
cs453-ca/CS453-2021-project/playground/runner.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @file runner.hpp
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2019 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Trivial program that call a function in several threads.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
void shared_access();
|
46
cs453-ca/CS453-2021-project/reference/Makefile
Normal file
46
cs453-ca/CS453-2021-project/reference/Makefile
Normal file
@@ -0,0 +1,46 @@
|
||||
BIN := ../$(notdir $(lastword $(abspath .))).so
|
||||
|
||||
EXT_H := h
|
||||
EXT_HPP := h hh hpp hxx h++
|
||||
EXT_C := c
|
||||
EXT_CXX := C cc cpp cxx c++
|
||||
|
||||
INCLUDE_DIR := ../include
|
||||
SOURCE_DIR := .
|
||||
|
||||
WILD_EXT = $(strip $(foreach EXT,$($(1)),$(wildcard $(2)/*.$(EXT))))
|
||||
|
||||
HDRS_C := $(call WILD_EXT,EXT_H,$(INCLUDE_DIR))
|
||||
HDRS_CXX := $(call WILD_EXT,EXT_HPP,$(INCLUDE_DIR))
|
||||
SRCS_C := $(call WILD_EXT,EXT_C,$(SOURCE_DIR))
|
||||
SRCS_CXX := $(call WILD_EXT,EXT_CXX,$(SOURCE_DIR))
|
||||
OBJS := $(SRCS_C:%=%.o) $(SRCS_CXX:%=%.o)
|
||||
|
||||
CC := $(CC)
|
||||
CCFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c11 -fPIC -I$(INCLUDE_DIR)
|
||||
CXX := $(CXX)
|
||||
CXXFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c++17 -fPIC -I$(INCLUDE_DIR)
|
||||
LD := $(if $(SRCS_CXX),$(CXX),$(CC))
|
||||
LDFLAGS := -shared
|
||||
LDLIBS :=
|
||||
|
||||
.PHONY: build clean
|
||||
|
||||
build: $(BIN)
|
||||
clean:
|
||||
$(RM) $(OBJS) $(BIN)
|
||||
|
||||
define BUILD_C
|
||||
%.$(1).o: %.$(1) $$(HDRS_C) Makefile
|
||||
$$(CC) $$(CCFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_C),$(eval $(call BUILD_C,$(EXT))))
|
||||
|
||||
define BUILD_CXX
|
||||
%.$(1).o: %.$(1) $$(HDRS_CXX) Makefile
|
||||
$$(CXX) $$(CXXFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_CXX),$(eval $(call BUILD_CXX,$(EXT))))
|
||||
|
||||
$(BIN): $(OBJS) Makefile
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
|
27
cs453-ca/CS453-2021-project/reference/lock.c
Normal file
27
cs453-ca/CS453-2021-project/reference/lock.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "lock.h"
|
||||
|
||||
bool lock_init(struct lock_t* lock) {
|
||||
return pthread_mutex_init(&(lock->mutex), NULL) == 0
|
||||
&& pthread_cond_init(&(lock->cv), NULL) == 0;
|
||||
}
|
||||
|
||||
void lock_cleanup(struct lock_t* lock) {
|
||||
pthread_mutex_destroy(&(lock->mutex));
|
||||
pthread_cond_destroy(&(lock->cv));
|
||||
}
|
||||
|
||||
bool lock_acquire(struct lock_t* lock) {
|
||||
return pthread_mutex_lock(&(lock->mutex)) == 0;
|
||||
}
|
||||
|
||||
void lock_release(struct lock_t* lock) {
|
||||
pthread_mutex_unlock(&(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wait(struct lock_t* lock) {
|
||||
pthread_cond_wait(&(lock->cv), &(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wake_up(struct lock_t* lock) {
|
||||
pthread_cond_broadcast(&(lock->cv));
|
||||
}
|
47
cs453-ca/CS453-2021-project/reference/lock.h
Normal file
47
cs453-ca/CS453-2021-project/reference/lock.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief A lock that can only be taken exclusively. Contrarily to shared locks,
|
||||
* exclusive locks have wait/wake_up capabilities.
|
||||
*/
|
||||
struct lock_t {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cv;
|
||||
};
|
||||
|
||||
/** Initialize the given lock.
|
||||
* @param lock Lock to initialize
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_init(struct lock_t* lock);
|
||||
|
||||
/** Clean up the given lock.
|
||||
* @param lock Lock to clean up
|
||||
**/
|
||||
void lock_cleanup(struct lock_t* lock);
|
||||
|
||||
/** Wait and acquire the given lock.
|
||||
* @param lock Lock to acquire
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_acquire(struct lock_t* lock);
|
||||
|
||||
/** Release the given lock.
|
||||
* @param lock Lock to release
|
||||
**/
|
||||
void lock_release(struct lock_t* lock);
|
||||
|
||||
/** Wait until woken up by a signal on the given lock.
|
||||
* The lock is released until lock_wait completes at which point it is acquired
|
||||
* again. Exclusive lock access is enforced.
|
||||
* @param lock Lock to release (until woken up) and wait on.
|
||||
**/
|
||||
void lock_wait(struct lock_t* lock);
|
||||
|
||||
/** Wake up all threads waiting on the given lock.
|
||||
* @param lock Lock on which other threads are waiting.
|
||||
**/
|
||||
void lock_wake_up(struct lock_t* lock);
|
36
cs453-ca/CS453-2021-project/reference/macros.h
Normal file
36
cs453-ca/CS453-2021-project/reference/macros.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
/** Define a proposition as likely true.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef likely
|
||||
#ifdef __GNUC__
|
||||
#define likely(prop) \
|
||||
__builtin_expect((prop) ? true : false, true /* likely */)
|
||||
#else
|
||||
#define likely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
/** Define a proposition as likely false.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef unlikely
|
||||
#ifdef __GNUC__
|
||||
#define unlikely(prop) \
|
||||
__builtin_expect((prop) ? true : false, false /* unlikely */)
|
||||
#else
|
||||
#define unlikely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
/** Define a variable as unused.
|
||||
**/
|
||||
#undef unused
|
||||
#ifdef __GNUC__
|
||||
#define unused(variable) \
|
||||
variable __attribute__((unused))
|
||||
#else
|
||||
#define unused(variable)
|
||||
#warning This compiler has no support for GCC attributes
|
||||
#endif
|
25
cs453-ca/CS453-2021-project/reference/shared-lock.c
Normal file
25
cs453-ca/CS453-2021-project/reference/shared-lock.c
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "shared-lock.h"
|
||||
|
||||
bool shared_lock_init(struct shared_lock_t* lock) {
|
||||
return pthread_rwlock_init(&lock->rwlock, NULL) == 0;
|
||||
}
|
||||
|
||||
void shared_lock_cleanup(struct shared_lock_t* lock) {
|
||||
pthread_rwlock_destroy(&lock->rwlock);
|
||||
}
|
||||
|
||||
bool shared_lock_acquire(struct shared_lock_t* lock) {
|
||||
return pthread_rwlock_wrlock(&lock->rwlock) == 0;
|
||||
}
|
||||
|
||||
void shared_lock_release(struct shared_lock_t* lock) {
|
||||
pthread_rwlock_unlock(&lock->rwlock);
|
||||
}
|
||||
|
||||
bool shared_lock_acquire_shared(struct shared_lock_t* lock) {
|
||||
return pthread_rwlock_rdlock(&lock->rwlock) == 0;
|
||||
}
|
||||
|
||||
void shared_lock_release_shared(struct shared_lock_t* lock) {
|
||||
pthread_rwlock_unlock(&lock->rwlock);
|
||||
}
|
53
cs453-ca/CS453-2021-project/reference/shared-lock.h
Normal file
53
cs453-ca/CS453-2021-project/reference/shared-lock.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
// Requested feature: pthread_rwlock_t
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#ifndef __USE_XOPEN2K
|
||||
#define __USE_XOPEN2K
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief A lock that can be taken exclusively but also shared. Contrarily to
|
||||
* exclusive locks, shared locks do not have wait/wake_up capabilities.
|
||||
*/
|
||||
struct shared_lock_t {
|
||||
pthread_rwlock_t rwlock;
|
||||
};
|
||||
|
||||
/** Initialize the given lock.
|
||||
* @param lock Lock to initialize
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool shared_lock_init(struct shared_lock_t* lock);
|
||||
|
||||
/** Clean the given lock up.
|
||||
* @param lock Lock to clean up
|
||||
**/
|
||||
void shared_lock_cleanup(struct shared_lock_t* lock);
|
||||
|
||||
/** Wait and acquire the given lock exclusively.
|
||||
* @param lock Lock to acquire
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool shared_lock_acquire(struct shared_lock_t* lock);
|
||||
|
||||
/** Release the given lock that has been taken exclusively.
|
||||
* @param lock Lock to release
|
||||
**/
|
||||
void shared_lock_release(struct shared_lock_t* lock);
|
||||
|
||||
/** Wait and acquire the given lock non-exclusively.
|
||||
* @param lock Lock to acquire
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool shared_lock_acquire_shared(struct shared_lock_t* lock);
|
||||
|
||||
/** Release the given lock that has been taken non-exclusively.
|
||||
* @param lock Lock to release
|
||||
**/
|
||||
void shared_lock_release_shared(struct shared_lock_t* lock);
|
198
cs453-ca/CS453-2021-project/reference/tm.c
Normal file
198
cs453-ca/CS453-2021-project/reference/tm.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @file tm.c
|
||||
* @author Sébastien Rouault <sebastien.rouault@epfl.ch>
|
||||
* @author Antoine Murat <antoine.murat@epfl.ch>
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright © 2018-2021 Sébastien Rouault.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Lock-based transaction manager implementation used as the reference.
|
||||
**/
|
||||
|
||||
// Requested feature: posix_memalign
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
// External headers
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Internal headers
|
||||
#include <tm.h>
|
||||
|
||||
#include "macros.h"
|
||||
#include "shared-lock.h"
|
||||
|
||||
static const tx_t read_only_tx = UINTPTR_MAX - 10;
|
||||
static const tx_t read_write_tx = UINTPTR_MAX - 11;
|
||||
|
||||
/**
|
||||
* @brief List of dynamically allocated segments.
|
||||
*/
|
||||
struct segment_node {
|
||||
struct segment_node* prev;
|
||||
struct segment_node* next;
|
||||
// uint8_t segment[] // segment of dynamic size
|
||||
};
|
||||
typedef struct segment_node* segment_list;
|
||||
|
||||
/**
|
||||
* @brief Simple Shared Memory Region (a.k.a Transactional Memory).
|
||||
*/
|
||||
struct region {
|
||||
struct shared_lock_t lock; // Global (coarse-grained) lock
|
||||
void* start; // Start of the shared memory region (i.e., of the non-deallocable memory segment)
|
||||
segment_list allocs; // Shared memory segments dynamically allocated via tm_alloc within transactions
|
||||
size_t size; // Size of the non-deallocable memory segment (in bytes)
|
||||
size_t align; // Size of a word in the shared memory region (in bytes)
|
||||
};
|
||||
|
||||
shared_t tm_create(size_t size, size_t align) {
|
||||
struct region* region = (struct region*) malloc(sizeof(struct region));
|
||||
if (unlikely(!region)) {
|
||||
return invalid_shared;
|
||||
}
|
||||
// We allocate the shared memory buffer such that its words are correctly
|
||||
// aligned.
|
||||
if (posix_memalign(&(region->start), align, size) != 0) {
|
||||
free(region);
|
||||
return invalid_shared;
|
||||
}
|
||||
if (!shared_lock_init(&(region->lock))) {
|
||||
free(region->start);
|
||||
free(region);
|
||||
return invalid_shared;
|
||||
}
|
||||
memset(region->start, 0, size);
|
||||
region->allocs = NULL;
|
||||
region->size = size;
|
||||
region->align = align;
|
||||
return region;
|
||||
}
|
||||
|
||||
void tm_destroy(shared_t shared) {
|
||||
// Note: To be compatible with any implementation, shared_t is defined as a
|
||||
// void*. For this particular implementation, the "real" type of a shared_t
|
||||
// is a struct region*.
|
||||
struct region* region = (struct region*) shared;
|
||||
while (region->allocs) { // Free allocated segments
|
||||
segment_list tail = region->allocs->next;
|
||||
free(region->allocs);
|
||||
region->allocs = tail;
|
||||
}
|
||||
free(region->start);
|
||||
shared_lock_cleanup(&(region->lock));
|
||||
free(region);
|
||||
}
|
||||
|
||||
// Note: In this particular implementation, tm_start returns a valid virtual
|
||||
// address (i.e., shared memory locations are virtually addressed).
|
||||
// This is NOT required. Indeed, as the content of shared memory is only ever
|
||||
// accessed via tm functions (read/write/free), you can use any naming scheme
|
||||
// you want to designate a word within the transactional memory as long as it
|
||||
// fits in a void*. Said functions will need to translate from a void* to a
|
||||
// specific word. Moreover, your naming scheme should support pointer arithmetic
|
||||
// (i.e., one should be able to pass tm_start(shared)+align*n to access the
|
||||
// (n+1)-th word within a memory region).
|
||||
// You can assume sizeof(void*) == 64b and that the maximum size ever allocated
|
||||
// will be 2^48.
|
||||
void* tm_start(shared_t shared) {
|
||||
return ((struct region*) shared)->start;
|
||||
}
|
||||
|
||||
size_t tm_size(shared_t shared) {
|
||||
return ((struct region*) shared)->size;
|
||||
}
|
||||
|
||||
size_t tm_align(shared_t shared) {
|
||||
return ((struct region*) shared)->align;
|
||||
}
|
||||
|
||||
tx_t tm_begin(shared_t shared, bool is_ro) {
|
||||
// We let read-only transactions run in parallel by acquiring a shared
|
||||
// access. On the other hand, read-write transactions acquire an exclusive
|
||||
// access. At any point in time, the lock can be shared between any number
|
||||
// of read-only transactions or held by a single read-write transaction.
|
||||
if (is_ro) {
|
||||
// Note: "unlikely" is a macro that helps branch prediction.
|
||||
// It tells the compiler (GCC) that the condition is unlikely to be true
|
||||
// and to optimize the code with this additional knowledge.
|
||||
// It of course penalizes executions in which the condition turns up to
|
||||
// be true.
|
||||
if (unlikely(!shared_lock_acquire_shared(&(((struct region*) shared)->lock))))
|
||||
return invalid_tx;
|
||||
return read_only_tx;
|
||||
} else {
|
||||
if (unlikely(!shared_lock_acquire(&(((struct region*) shared)->lock))))
|
||||
return invalid_tx;
|
||||
return read_write_tx;
|
||||
}
|
||||
}
|
||||
|
||||
bool tm_end(shared_t shared, tx_t tx) {
|
||||
if (tx == read_only_tx) {
|
||||
shared_lock_release_shared(&(((struct region*) shared)->lock));
|
||||
} else {
|
||||
shared_lock_release(&(((struct region*) shared)->lock));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: "unused" is a macro that tells the compiler that a variable is unused.
|
||||
bool tm_read(shared_t unused(shared), tx_t unused(tx), void const* source, size_t size, void* target) {
|
||||
memcpy(target, source, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tm_write(shared_t unused(shared), tx_t unused(tx), void const* source, size_t size, void* target) {
|
||||
memcpy(target, source, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
alloc_t tm_alloc(shared_t shared, tx_t unused(tx), size_t size, void** target) {
|
||||
// We allocate the dynamic segment such that its words are correctly
|
||||
// aligned. Moreover, the alignment of the 'next' and 'prev' pointers must
|
||||
// be satisfied. Thus, we use align on max(align, struct segment_node*).
|
||||
size_t align = ((struct region*) shared)->align;
|
||||
align = align < sizeof(struct segment_node*) ? sizeof(void*) : align;
|
||||
|
||||
struct segment_node* sn;
|
||||
if (unlikely(posix_memalign((void**)&sn, align, sizeof(struct segment_node) + size) != 0)) // Allocation failed
|
||||
return nomem_alloc;
|
||||
|
||||
// Insert in the linked list
|
||||
sn->prev = NULL;
|
||||
sn->next = ((struct region*) shared)->allocs;
|
||||
if (sn->next) sn->next->prev = sn;
|
||||
((struct region*) shared)->allocs = sn;
|
||||
|
||||
void* segment = (void*) ((uintptr_t) sn + sizeof(struct segment_node));
|
||||
memset(segment, 0, size);
|
||||
*target = segment;
|
||||
return success_alloc;
|
||||
}
|
||||
|
||||
bool tm_free(shared_t shared, tx_t unused(tx), void* segment) {
|
||||
struct segment_node* sn = (struct segment_node*) ((uintptr_t) segment - sizeof(struct segment_node));
|
||||
|
||||
// Remove from the linked list
|
||||
if (sn->prev) sn->prev->next = sn->next;
|
||||
else ((struct region*) shared)->allocs = sn->next;
|
||||
if (sn->next) sn->next->prev = sn->prev;
|
||||
|
||||
free(sn);
|
||||
return true;
|
||||
}
|
236
cs453-ca/CS453-2021-project/submit.py
Normal file
236
cs453-ca/CS453-2021-project/submit.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# coding: utf-8
|
||||
###
|
||||
# @file submit.py
|
||||
# @author Sébastien Rouault <sebastien.rouault@alumni.epfl.ch>
|
||||
#
|
||||
# @section LICENSE
|
||||
#
|
||||
# Copyright © 2018-2019 École Polytechnique Fédérale de Lausanne (EPFL).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# any later version. Please see https://gnu.org/licenses/gpl.html
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# @section DESCRIPTION
|
||||
#
|
||||
# Client for the automated performance measurement tool of CS-453.
|
||||
###
|
||||
|
||||
if __name__ != "__main__":
|
||||
raise RuntimeError("Script " + repr(__file__) + " is to be used as the main module only")
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Python version check and imports
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info.major != 3 or sys.version_info.minor < 5:
|
||||
print("WARNING: python interpreter not supported, please install version 3.5 or compatible (e.g. 3.6, 3.7, etc)")
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import pathlib
|
||||
import socket
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Configuration
|
||||
|
||||
version_uid = b"\x00\x00\x00\x02" # Unique version identifier (must be identical in compatible server)
|
||||
|
||||
default_host = "lpdxeon2680.epfl.ch" # Default server hostname or IPv4
|
||||
default_port = 9997 # Default server TCP port
|
||||
|
||||
max_codesize = 100000 # Max code size (in bytes) before AND after deflate (the same as in the server); modifying this value won't change the behavior of the server ;)
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Command line
|
||||
|
||||
# Description
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument("--uuid",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Secret user unique identifier")
|
||||
parser.add_argument("--host",
|
||||
type=str,
|
||||
default=default_host,
|
||||
help="Server hostname")
|
||||
parser.add_argument("--port",
|
||||
type=int,
|
||||
default=default_port,
|
||||
help="Server TCP port")
|
||||
parser.add_argument("--force-update",
|
||||
action="store_true",
|
||||
help="Update your submission to the server; no validation of the correctness of your submission will be performed")
|
||||
parser.add_argument("--download",
|
||||
action="store_true",
|
||||
help="Download your submission currently kept for grading under 'zippath' (instead of submitting a new one); if '--force-update' is used, a \"swap\" is performed")
|
||||
parser.add_argument("zippath",
|
||||
type=str,
|
||||
help="Path to a zip file containing your library code, or that will be overwritten with '--download'")
|
||||
|
||||
# Command line parsing
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Socket helper
|
||||
|
||||
def socket_encode_size(size):
|
||||
""" Encode the given number into 4 bytes.
|
||||
Args:
|
||||
size Size to encode
|
||||
Returns:
|
||||
Encoded size
|
||||
"""
|
||||
# Assertions
|
||||
if size < 0 or size >= (1 << 32):
|
||||
raise OverflowError
|
||||
# Encoding
|
||||
res = bytearray(4)
|
||||
for i in range(4):
|
||||
res[i] = size & 0xFF
|
||||
size >>= 8
|
||||
return res
|
||||
|
||||
def socket_decode_size(size):
|
||||
""" Decode the given 4-byte encoded size into an integer.
|
||||
Args:
|
||||
size Encoded size
|
||||
Returns:
|
||||
Decoded size
|
||||
"""
|
||||
# Assertions
|
||||
if len(size) != 4:
|
||||
raise OverflowError
|
||||
# Decoding
|
||||
res = 0
|
||||
for i in range(4):
|
||||
res <<= 8
|
||||
res += size[3 - i]
|
||||
return res
|
||||
|
||||
def socket_consume(fd, size):
|
||||
""" Repeatedly read the socket until the given size has been received.
|
||||
Args:
|
||||
fd Socket file descriptor to read
|
||||
size Size to read from the socket
|
||||
"""
|
||||
data = bytes()
|
||||
while size > 0:
|
||||
recv = fd.recv(size)
|
||||
if len(recv) <= 0:
|
||||
raise IOError("No more data in the socket")
|
||||
data += recv
|
||||
size -= len(recv)
|
||||
return data
|
||||
|
||||
def socket_recvfield(fd, maxsize, exact=False):
|
||||
""" Receive a field from a socket.
|
||||
Args:
|
||||
fd Socket file descriptor to read
|
||||
maxsize Maximum/exact size to accept
|
||||
exact Whether field size was exact
|
||||
Returns:
|
||||
Received field bytes
|
||||
"""
|
||||
size = socket_decode_size(socket_consume(fd, 4))
|
||||
if size > maxsize:
|
||||
raise IOError("Field is too large")
|
||||
elif exact and size < maxsize:
|
||||
raise IOError("Field is too small")
|
||||
return socket_consume(fd, size)
|
||||
|
||||
def socket_sendfield(fd, data):
|
||||
""" Send a field through a socket.
|
||||
Args:
|
||||
fd Socket file descriptor to write
|
||||
data Data bytes to send
|
||||
"""
|
||||
if fd.sendall(socket_encode_size(len(data))) is not None or fd.sendall(data) is not None:
|
||||
raise IOError("Send failed")
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Client
|
||||
|
||||
# Open connection with the server
|
||||
client_fd = None
|
||||
for af, socktype, proto, canonname, sa in socket.getaddrinfo(args.host, args.port, socket.AF_INET, socket.SOCK_STREAM):
|
||||
try:
|
||||
client_fd = socket.socket(af, socktype, proto)
|
||||
except OSError as msg:
|
||||
client_fd = None
|
||||
continue
|
||||
try:
|
||||
client_fd.connect(sa)
|
||||
except OSError as msg:
|
||||
client_fd.close()
|
||||
client_fd = None
|
||||
continue
|
||||
break
|
||||
if client_fd is None:
|
||||
print("""Unable to connect to %s:%s
|
||||
Message to the students:
|
||||
Please make sure you operate from EPFL's network (i.e. either on campus or through the VPN).
|
||||
If you do, it is not unlikely that the server machine is temporarily being used for another purpose.
|
||||
Please retry later; contact the TAs only if the problem persists for more than a day.""" % (args.host, args.port))
|
||||
exit(1)
|
||||
atexit.register(lambda: client_fd.close())
|
||||
|
||||
# Check version
|
||||
if socket_recvfield(client_fd, len(version_uid)) != version_uid:
|
||||
print("Protocol version mismatch with the server, please pull/download the latest version of the client.")
|
||||
exit(1)
|
||||
|
||||
# Send the secret user identifier
|
||||
socket_sendfield(client_fd, args.uuid.encode())
|
||||
res = socket_recvfield(client_fd, 1, exact=True)
|
||||
msg = {1: "Invalid user secret identifier", 2: "Unknown user secret identifier", 3: "User is already logged in"}
|
||||
if res[0] in msg:
|
||||
print(msg[res[0]]) # Unsuccessful identifications are logged
|
||||
exit(1)
|
||||
|
||||
# Send mode of submission
|
||||
submit_mode = (1 if args.download else 0) + (2 if args.force_update else 0)
|
||||
socket_sendfield(client_fd, submit_mode.to_bytes(1, "little"))
|
||||
|
||||
# Process according to mode of submission
|
||||
if args.force_update or not args.download:
|
||||
# Send zip file
|
||||
zip_path = pathlib.Path(args.zippath)
|
||||
if not zip_path.exists():
|
||||
print("File %r cannot be accessed" % str(zip_path))
|
||||
exit(1)
|
||||
socket_sendfield(client_fd, zip_path.read_bytes())
|
||||
if args.download:
|
||||
# Receive zip file
|
||||
with pathlib.Path(args.zippath).open("wb") as fd:
|
||||
fd.write(socket_recvfield(client_fd, max_codesize))
|
||||
|
||||
# Read until the socket is closed (for user feedback/error messages)
|
||||
try:
|
||||
prev = bytes()
|
||||
while True:
|
||||
data = client_fd.recv(256)
|
||||
if len(data) <= 0:
|
||||
# Here 'prev' should be empty or not enough data to decode 'prev' correctly: so do nothing
|
||||
break
|
||||
data = prev + data
|
||||
prev = bytes()
|
||||
try:
|
||||
text = data.decode()
|
||||
except UnicodeDecodeError as err:
|
||||
prev = data[err.start:]
|
||||
text = data[:err.start].decode()
|
||||
if len(text) > 0:
|
||||
sys.stdout.write(text)
|
||||
sys.stdout.flush()
|
||||
except ConnectionResetError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
15
cs453-ca/CS453-2021-project/sync-examples/Makefile
Normal file
15
cs453-ca/CS453-2021-project/sync-examples/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
LDFLAGS += -pthread
|
||||
|
||||
BIN=counter1 counter2 counter3 counter4 election1 election2 election3 election4 procon1 procon2 procon3 procon4
|
||||
|
||||
all: ${BIN}
|
||||
.PHONY: all
|
||||
|
||||
clean:
|
||||
rm *.o ${BIN}
|
||||
.PHONY: clean
|
||||
|
||||
counter2: lock.o
|
||||
election2: lock.o
|
||||
procon2: lock.o
|
||||
procon4: lock.o
|
73
cs453-ca/CS453-2021-project/sync-examples/README.md
Normal file
73
cs453-ca/CS453-2021-project/sync-examples/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Simple synchronization examples.
|
||||
|
||||
Modifying a variable that is concurrently accessed by another thread is
|
||||
dangerous and will (most of the time) lead to Undefined Behaviors and Data
|
||||
Races.
|
||||
|
||||
Through simple examples, we'll see how to use synchronization primitives to
|
||||
build thread-safe programs using locks and atomic variables.
|
||||
|
||||
We build 3 examples:
|
||||
- A counter. Each thread increments a shared counter repeatedly RUNS times.
|
||||
We check that the counter = RUNS * THREADS at the end.
|
||||
- Leader election. In each run, one thread is elected as the leader. We check
|
||||
that exactly one thread considers itself as the leader in each run.
|
||||
- Producer-Consumer. One thread generates data in a circular buffer while the
|
||||
second tries to consume it. They have to be kept in sync.
|
||||
|
||||
These examples also show how to start threads and wait for their completion.
|
||||
YOU DON'T HAVE TO START THREADS YOURSELVES IN THE DUAL-VERSIONED STM ALGO.
|
||||
In the STM, threads are started by the users of your library (in which they
|
||||
repeatedly (1) start a transaction, (2) read/write/alloc/free STM memory and (3)
|
||||
commit the transaction.).
|
||||
|
||||
## Counter
|
||||
|
||||
### Bad approach #1
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Good approach #1
|
||||
We take a big nice lock around the ++: correct.
|
||||
|
||||
### Bad approach #2
|
||||
We use an atomic variable but use 2 non atomic operations: not UB, but doesn't
|
||||
work.
|
||||
|
||||
### Good approach #2
|
||||
We use an atomic variable and an atomic operation (fetch and add): correct.
|
||||
|
||||
## Leader election
|
||||
|
||||
### Bad approch # 1
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Good approach #1
|
||||
We take a big nice lock around the test and set: correct.
|
||||
|
||||
### Bad approach #2
|
||||
We use an atomic variable but use 2 non atomic operations: not UB, but doesn't
|
||||
work.
|
||||
|
||||
### Good approach #2
|
||||
We use an atomic variable and an atomic operation (compare and swap): correct.
|
||||
|
||||
## Producer-consumer
|
||||
|
||||
### Bad approach
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Okayish approach #1
|
||||
We take a big nice lock when when writting/reading and checking the bounds:
|
||||
correct.
|
||||
|
||||
### Okayish approach #2
|
||||
We use atomic variables and fences: correct.
|
||||
|
||||
### Good approach
|
||||
We use a conditional variable. :)
|
||||
https://www.ibm.com/docs/en/i/7.1?topic=ssw_ibm_i_71/apis/users_78.htm
|
||||
Conditional variables are synchronization primitives provided by the kernel
|
||||
that let a thread sleep until it's woken up. Using a "cond var", a consummer
|
||||
that realizes that data has not been generated yet can go to sleep instead of
|
||||
busy waiting. It will then be woken up by the producer once the data is
|
||||
generated. :)
|
42
cs453-ca/CS453-2021-project/sync-examples/counter1.c
Normal file
42
cs453-ca/CS453-2021-project/sync-examples/counter1.c
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
counter++;
|
||||
// Equivalent to:
|
||||
//
|
||||
// int local = counter;
|
||||
// counter = local + 1;
|
||||
//
|
||||
// This is not thread-safe as the increment is not atomic.
|
||||
// **Incorrect**
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
45
cs453-ca/CS453-2021-project/sync-examples/counter2.c
Normal file
45
cs453-ca/CS453-2021-project/sync-examples/counter2.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
static int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
counter++;
|
||||
lock_release(&lock);
|
||||
// Still equivalent, but only one thread accesses counter at a time, so it's
|
||||
// thread safe, no concurrent access/data race. :)
|
||||
// **Correct**
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
38
cs453-ca/CS453-2021-project/sync-examples/counter3.c
Normal file
38
cs453-ca/CS453-2021-project/sync-examples/counter3.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
int read_copy = counter;
|
||||
counter = read_copy + 1;
|
||||
// **Incorrect**, using atomic variables (~atomic registers is not enough).
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
37
cs453-ca/CS453-2021-project/sync-examples/counter4.c
Normal file
37
cs453-ca/CS453-2021-project/sync-examples/counter4.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
atomic_fetch_add(&counter, 1);
|
||||
// **Correct**, using an atomic operation (most likely) provided by the CPU.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election1.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election1.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
// For each run (or round) we try to impose ourselves as the run's leader.
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Incorrect**, super wrong, plz synchronize.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
54
cs453-ca/CS453-2021-project/sync-examples/election2.c
Normal file
54
cs453-ca/CS453-2021-project/sync-examples/election2.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
static int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
lock_release(&lock);
|
||||
// **Correct**, a single thread tries to become leader at a time.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election3.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election3.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Incorrect**, atomic variables (~atomic registers) do not enforce atomic
|
||||
// operation blocks.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election4.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election4.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
int expected = 0;
|
||||
if (atomic_compare_exchange_strong(&leader[r], &expected, tid)) {
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Correct**, we use the Compare and Swap operation to atomically check if
|
||||
// a leader has already been elected and if not, we impose ourselves. :)
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
27
cs453-ca/CS453-2021-project/sync-examples/lock.c
Normal file
27
cs453-ca/CS453-2021-project/sync-examples/lock.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "lock.h"
|
||||
|
||||
bool lock_init(struct lock_t* lock) {
|
||||
return pthread_mutex_init(&(lock->mutex), NULL) == 0
|
||||
&& pthread_cond_init(&(lock->cv), NULL) == 0;
|
||||
}
|
||||
|
||||
void lock_cleanup(struct lock_t* lock) {
|
||||
pthread_mutex_destroy(&(lock->mutex));
|
||||
pthread_cond_destroy(&(lock->cv));
|
||||
}
|
||||
|
||||
bool lock_acquire(struct lock_t* lock) {
|
||||
return pthread_mutex_lock(&(lock->mutex)) == 0;
|
||||
}
|
||||
|
||||
void lock_release(struct lock_t* lock) {
|
||||
pthread_mutex_unlock(&(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wait(struct lock_t* lock) {
|
||||
pthread_cond_wait(&(lock->cv), &(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wake_up(struct lock_t* lock) {
|
||||
pthread_cond_broadcast(&(lock->cv));
|
||||
}
|
47
cs453-ca/CS453-2021-project/sync-examples/lock.h
Normal file
47
cs453-ca/CS453-2021-project/sync-examples/lock.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief A lock that can only be taken exclusively. Contrarily to shared locks,
|
||||
* exclusive locks have wait/wake_up capabilities.
|
||||
*/
|
||||
struct lock_t {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cv;
|
||||
};
|
||||
|
||||
/** Initialize the given lock.
|
||||
* @param lock Lock to initialize
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_init(struct lock_t* lock);
|
||||
|
||||
/** Clean up the given lock.
|
||||
* @param lock Lock to clean up
|
||||
**/
|
||||
void lock_cleanup(struct lock_t* lock);
|
||||
|
||||
/** Wait and acquire the given lock.
|
||||
* @param lock Lock to acquire
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_acquire(struct lock_t* lock);
|
||||
|
||||
/** Release the given lock.
|
||||
* @param lock Lock to release
|
||||
**/
|
||||
void lock_release(struct lock_t* lock);
|
||||
|
||||
/** Wait until woken up by a signal on the given lock.
|
||||
* The lock is released until lock_wait completes at which point it is acquired
|
||||
* again. Exclusive lock access is enforced.
|
||||
* @param lock Lock to release (until woken up) and wait on.
|
||||
**/
|
||||
void lock_wait(struct lock_t* lock);
|
||||
|
||||
/** Wake up all threads waiting on the given lock.
|
||||
* @param lock Lock on which other threads are waiting.
|
||||
**/
|
||||
void lock_wake_up(struct lock_t* lock);
|
72
cs453-ca/CS453-2021-project/sync-examples/procon1.c
Normal file
72
cs453-ca/CS453-2021-project/sync-examples/procon1.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
// Super wrong: Overwritting data being read by the consumer.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
// Super wrong: Reading data concurrently (or not even) written by the
|
||||
// producer.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
97
cs453-ca/CS453-2021-project/sync-examples/procon2.c
Normal file
97
cs453-ca/CS453-2021-project/sync-examples/procon2.c
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
int produced_until = 0;
|
||||
int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
lock_acquire(&lock);
|
||||
if (consumed_until + BUFFER_SIZE > r) break;
|
||||
lock_release(&lock);
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
produced_until++;
|
||||
lock_release(&lock);
|
||||
// Correct: we wait until nobody consumed data anymore and correctly release
|
||||
// (prevent reordering) thanks to the lock.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
lock_acquire(&lock);
|
||||
if (produced_until > r) break;
|
||||
lock_release(&lock);
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
consumed_until++;
|
||||
lock_release(&lock);
|
||||
// Correct: we wait until nobody produces data anymore and correctly acquire
|
||||
// + release (prevent reordering) thanks to the lock.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
91
cs453-ca/CS453-2021-project/sync-examples/procon3.c
Normal file
91
cs453-ca/CS453-2021-project/sync-examples/procon3.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
atomic_int produced_until = 0;
|
||||
atomic_int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
// The memory order can be relaxed as we don't read anything "produced"
|
||||
// by the consumer.
|
||||
int local_cu = atomic_load_explicit(&consumed_until, memory_order_relaxed);
|
||||
if (local_cu + BUFFER_SIZE > r) break;
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
// We want to increment "produced_until" after the buffer has been written.
|
||||
// By using memory_order_release, we prevent the STOREs on buffer from being
|
||||
// reordered after the atomic operation.
|
||||
atomic_fetch_add_explicit(&produced_until, 1, memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
// We don't want to access the buffer before checking the atomic variable.
|
||||
// The memory_order_acquire prevents this reordering.
|
||||
int local_pu = atomic_load_explicit(&produced_until, memory_order_acquire);
|
||||
if (local_pu > r) break;
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
atomic_fetch_add_explicit(&consumed_until, 1, memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
103
cs453-ca/CS453-2021-project/sync-examples/procon4.c
Normal file
103
cs453-ca/CS453-2021-project/sync-examples/procon4.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 8
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
int produced_until = 0;
|
||||
int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
while (true) {
|
||||
if (consumed_until + BUFFER_SIZE > r) break;
|
||||
lock_wait(&lock);
|
||||
// Note: waiting releases the lock and puts the thread to sleep. Once
|
||||
// woken up, it will need to acquire back the lock to continue its
|
||||
// execution.
|
||||
// Said differently, if multiple threads are waiting, upon "lock_wake_up",
|
||||
// only one of them will continue the execution at a time (because the
|
||||
// lock is exclusive).
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
produced_until++;
|
||||
lock_release(&lock);
|
||||
lock_wake_up(&lock); // We tell the consumer it can continue consuming.
|
||||
// Correct: This is a better version of the lock one in which we do not busy
|
||||
// wait but rely on a notification primitive to let the "idle" cores rest.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
while (true) {
|
||||
if (produced_until > r) break;
|
||||
lock_wait(&lock);
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
consumed_until++;
|
||||
lock_release(&lock);
|
||||
lock_wake_up(&lock); // We tell the producer it can continue producing.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
46
cs453-ca/CS453-2021-project/template/Makefile
Normal file
46
cs453-ca/CS453-2021-project/template/Makefile
Normal file
@@ -0,0 +1,46 @@
|
||||
BIN := ../$(notdir $(lastword $(abspath .))).so
|
||||
|
||||
EXT_H := h
|
||||
EXT_HPP := h hh hpp hxx h++
|
||||
EXT_C := c
|
||||
EXT_CXX := C cc cpp cxx c++
|
||||
|
||||
INCLUDE_DIR := ../include
|
||||
SOURCE_DIR := .
|
||||
|
||||
WILD_EXT = $(strip $(foreach EXT,$($(1)),$(wildcard $(2)/*.$(EXT))))
|
||||
|
||||
HDRS_C := $(call WILD_EXT,EXT_H,$(INCLUDE_DIR))
|
||||
HDRS_CXX := $(call WILD_EXT,EXT_HPP,$(INCLUDE_DIR))
|
||||
SRCS_C := $(call WILD_EXT,EXT_C,$(SOURCE_DIR))
|
||||
SRCS_CXX := $(call WILD_EXT,EXT_CXX,$(SOURCE_DIR))
|
||||
OBJS := $(SRCS_C:%=%.o) $(SRCS_CXX:%=%.o)
|
||||
|
||||
CC := $(CC)
|
||||
CCFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c11 -fPIC -I$(INCLUDE_DIR)
|
||||
CXX := $(CXX)
|
||||
CXXFLAGS := -Wall -Wextra -Wfatal-errors -O2 -std=c++17 -fPIC -I$(INCLUDE_DIR)
|
||||
LD := $(if $(SRCS_CXX),$(CXX),$(CC))
|
||||
LDFLAGS := -shared
|
||||
LDLIBS :=
|
||||
|
||||
.PHONY: build clean
|
||||
|
||||
build: $(BIN)
|
||||
clean:
|
||||
$(RM) $(OBJS) $(BIN)
|
||||
|
||||
define BUILD_C
|
||||
%.$(1).o: %.$(1) $$(HDRS_C) Makefile
|
||||
$$(CC) $$(CCFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_C),$(eval $(call BUILD_C,$(EXT))))
|
||||
|
||||
define BUILD_CXX
|
||||
%.$(1).o: %.$(1) $$(HDRS_CXX) Makefile
|
||||
$$(CXX) $$(CXXFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
$(foreach EXT,$(EXT_CXX),$(eval $(call BUILD_CXX,$(EXT))))
|
||||
|
||||
$(BIN): $(OBJS) Makefile
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
|
36
cs453-ca/CS453-2021-project/template/macros.h
Normal file
36
cs453-ca/CS453-2021-project/template/macros.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
/** Define a proposition as likely true.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef likely
|
||||
#ifdef __GNUC__
|
||||
#define likely(prop) \
|
||||
__builtin_expect((prop) ? true : false, true /* likely */)
|
||||
#else
|
||||
#define likely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
/** Define a proposition as likely false.
|
||||
* @param prop Proposition
|
||||
**/
|
||||
#undef unlikely
|
||||
#ifdef __GNUC__
|
||||
#define unlikely(prop) \
|
||||
__builtin_expect((prop) ? true : false, false /* unlikely */)
|
||||
#else
|
||||
#define unlikely(prop) \
|
||||
(prop)
|
||||
#endif
|
||||
|
||||
/** Define a variable as unused.
|
||||
**/
|
||||
#undef unused
|
||||
#ifdef __GNUC__
|
||||
#define unused(variable) \
|
||||
variable __attribute__((unused))
|
||||
#else
|
||||
#define unused(variable)
|
||||
#warning This compiler has no support for GCC attributes
|
||||
#endif
|
141
cs453-ca/CS453-2021-project/template/tm.c
Normal file
141
cs453-ca/CS453-2021-project/template/tm.c
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @file tm.c
|
||||
* @author Cedric Hölzl
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* [...]
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Implementation of your own transaction manager.
|
||||
* You can completely rewrite this file (and create more files) as you wish.
|
||||
* Only the interface (i.e. exported symbols and semantic) must be preserved.
|
||||
**/
|
||||
|
||||
// Requested features
|
||||
#define _GNU_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#ifdef __STDC_NO_ATOMICS__
|
||||
#error Current C11 compiler does not support atomic operations
|
||||
#endif
|
||||
|
||||
// External headers
|
||||
|
||||
// Internal headers
|
||||
#include <tm.h>
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
/** Create (i.e. allocate + init) a new shared memory region, with one first non-free-able allocated segment of the requested size and alignment.
|
||||
* @param size Size of the first shared segment of memory to allocate (in bytes), must be a positive multiple of the alignment
|
||||
* @param align Alignment (in bytes, must be a power of 2) that the shared memory region must support
|
||||
* @return Opaque shared memory region handle, 'invalid_shared' on failure
|
||||
**/
|
||||
shared_t tm_create(size_t unused(size), size_t unused(align)) {
|
||||
// TODO: tm_create(size_t, size_t)
|
||||
return invalid_shared;
|
||||
}
|
||||
|
||||
/** Destroy (i.e. clean-up + free) a given shared memory region.
|
||||
* @param shared Shared memory region to destroy, with no running transaction
|
||||
**/
|
||||
void tm_destroy(shared_t unused(shared)) {
|
||||
// TODO: tm_destroy(shared_t)
|
||||
}
|
||||
|
||||
/** [thread-safe] Return the start address of the first allocated segment in the shared memory region.
|
||||
* @param shared Shared memory region to query
|
||||
* @return Start address of the first allocated segment
|
||||
**/
|
||||
void* tm_start(shared_t unused(shared)) {
|
||||
// TODO: tm_start(shared_t)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** [thread-safe] Return the size (in bytes) of the first allocated segment of the shared memory region.
|
||||
* @param shared Shared memory region to query
|
||||
* @return First allocated segment size
|
||||
**/
|
||||
size_t tm_size(shared_t unused(shared)) {
|
||||
// TODO: tm_size(shared_t)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** [thread-safe] Return the alignment (in bytes) of the memory accesses on the given shared memory region.
|
||||
* @param shared Shared memory region to query
|
||||
* @return Alignment used globally
|
||||
**/
|
||||
size_t tm_align(shared_t unused(shared)) {
|
||||
// TODO: tm_align(shared_t)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** [thread-safe] Begin a new transaction on the given shared memory region.
|
||||
* @param shared Shared memory region to start a transaction on
|
||||
* @param is_ro Whether the transaction is read-only
|
||||
* @return Opaque transaction ID, 'invalid_tx' on failure
|
||||
**/
|
||||
tx_t tm_begin(shared_t unused(shared), bool unused(is_ro)) {
|
||||
// TODO: tm_begin(shared_t)
|
||||
return invalid_tx;
|
||||
}
|
||||
|
||||
/** [thread-safe] End the given transaction.
|
||||
* @param shared Shared memory region associated with the transaction
|
||||
* @param tx Transaction to end
|
||||
* @return Whether the whole transaction committed
|
||||
**/
|
||||
bool tm_end(shared_t unused(shared), tx_t unused(tx)) {
|
||||
// TODO: tm_end(shared_t, tx_t)
|
||||
return false;
|
||||
}
|
||||
|
||||
/** [thread-safe] Read operation in the given transaction, source in the shared region and target in a private region.
|
||||
* @param shared Shared memory region associated with the transaction
|
||||
* @param tx Transaction to use
|
||||
* @param source Source start address (in the shared region)
|
||||
* @param size Length to copy (in bytes), must be a positive multiple of the alignment
|
||||
* @param target Target start address (in a private region)
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
bool tm_read(shared_t unused(shared), tx_t unused(tx), void const* unused(source), size_t unused(size), void* unused(target)) {
|
||||
// TODO: tm_read(shared_t, tx_t, void const*, size_t, void*)
|
||||
return false;
|
||||
}
|
||||
|
||||
/** [thread-safe] Write operation in the given transaction, source in a private region and target in the shared region.
|
||||
* @param shared Shared memory region associated with the transaction
|
||||
* @param tx Transaction to use
|
||||
* @param source Source start address (in a private region)
|
||||
* @param size Length to copy (in bytes), must be a positive multiple of the alignment
|
||||
* @param target Target start address (in the shared region)
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
bool tm_write(shared_t unused(shared), tx_t unused(tx), void const* unused(source), size_t unused(size), void* unused(target)) {
|
||||
// TODO: tm_write(shared_t, tx_t, void const*, size_t, void*)
|
||||
return false;
|
||||
}
|
||||
|
||||
/** [thread-safe] Memory allocation in the given transaction.
|
||||
* @param shared Shared memory region associated with the transaction
|
||||
* @param tx Transaction to use
|
||||
* @param size Allocation requested size (in bytes), must be a positive multiple of the alignment
|
||||
* @param target Pointer in private memory receiving the address of the first byte of the newly allocated, aligned segment
|
||||
* @return Whether the whole transaction can continue (success/nomem), or not (abort_alloc)
|
||||
**/
|
||||
alloc_t tm_alloc(shared_t unused(shared), tx_t unused(tx), size_t unused(size), void** unused(target)) {
|
||||
// TODO: tm_alloc(shared_t, tx_t, size_t, void**)
|
||||
return abort_alloc;
|
||||
}
|
||||
|
||||
/** [thread-safe] Memory freeing in the given transaction.
|
||||
* @param shared Shared memory region associated with the transaction
|
||||
* @param tx Transaction to use
|
||||
* @param target Address of the first byte of the previously allocated segment to deallocate
|
||||
* @return Whether the whole transaction can continue
|
||||
**/
|
||||
bool tm_free(shared_t unused(shared), tx_t unused(tx), void* unused(target)) {
|
||||
// TODO: tm_free(shared_t, tx_t, void*)
|
||||
return false;
|
||||
}
|
Reference in New Issue
Block a user