diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh
index 1fcd49beb79603e6d79cf35e1e4b9f699fcb2f49..89dfd5e277f9811bd91cdc363a61f41a1e81b706 100755
--- a/mechanisms/generate.sh
+++ b/mechanisms/generate.sh
@@ -1,3 +1,5 @@
+#!/usr/bin/env bash
+
 #flags="-t cpu -O"
 flags="-t cpu"
 
diff --git a/miniapp/io.cpp b/miniapp/io.cpp
index 6b81a62c853fbc2098d343c0c61b926903f47c52..fbc70ef823a660c4a14cb4610f1dd011251e4f2b 100644
--- a/miniapp/io.cpp
+++ b/miniapp/io.cpp
@@ -113,8 +113,27 @@ static void update_option(util::optional<T>& opt, const nlohmann::json& j, const
 cl_options read_options(int argc, char** argv) {
 
     // Default options:
-    const cl_options defopts{1000, 500, "expsyn", 100, 100., 0.025, false,
-                             false, 1.0, "trace_", util::nothing};
+    const cl_options defopts{
+        1000,       // number of cells
+        500,        // synapses_per_cell
+        "expsyn",   // synapse type
+        100,        // compartments_per_segment
+        100.,       // tfinal
+        0.025,      // dt
+        false,      // all_to_all
+        false,      // probe_soma_only
+        1.0,        // probe_ratio
+        "trace_",   // trace_prefix
+        util::nothing,  // trace_max_gid
+
+        // spike_output_parameters:
+        false,      // spike output
+        false,      // single_file_per_simulation
+        true,       // Overwrite outputfile if exists
+        "./",       // output path
+        "spikes",   // file name
+        "gdf"       // file extention
+    };
 
     cl_options options;
     std::string save_file = "";
@@ -189,6 +208,17 @@ cl_options read_options(int argc, char** argv) {
                     update_option(options.probe_soma_only, fopts, "probe_soma_only");
                     update_option(options.trace_prefix, fopts, "trace_prefix");
                     update_option(options.trace_max_gid, fopts, "trace_max_gid");
+
+                    // Parameters for spike output
+                    update_option(options.spike_file_output, fopts, "spike_file_output");
+                    if (options.spike_file_output) {
+                        update_option(options.single_file_per_rank, fopts, "single_file_per_rank");
+                        update_option(options.over_write, fopts, "over_write");
+                        update_option(options.output_path, fopts, "output_path");
+                        update_option(options.file_name, fopts, "file_name");
+                        update_option(options.file_extention, fopts, "file_extention");
+                    }
+
                 }
                 catch (std::exception& e) {
                     throw model_description_error(
@@ -241,8 +271,8 @@ cl_options read_options(int argc, char** argv) {
                 else {
                     fopts["trace_max_gid"] = nullptr;
                 }
-
                 fid << std::setw(3) << fopts << "\n";
+
             }
             catch (std::exception& e) {
                 throw model_description_error(
@@ -257,7 +287,7 @@ cl_options read_options(int argc, char** argv) {
 }
 
 std::ostream& operator<<(std::ostream& o, const cl_options& options) {
-    o << "simultion options:\n";
+    o << "simulation options:\n";
     o << "  cells                : " << options.cells << "\n";
     o << "  compartments/segment : " << options.compartments_per_segment << "\n";
     o << "  synapses/cell        : " << options.synapses_per_cell << "\n";
diff --git a/miniapp/io.hpp b/miniapp/io.hpp
index 510a0611c9e72c4084cf3487579a1a5b239efadc..9de6299f2a582dccda0913fb520cf133d2546bc4 100644
--- a/miniapp/io.hpp
+++ b/miniapp/io.hpp
@@ -25,6 +25,14 @@ struct cl_options {
     double probe_ratio;
     std::string trace_prefix;
     util::optional<unsigned> trace_max_gid;
+
+    // Parameters for spike output
+    bool spike_file_output;
+    bool single_file_per_rank;
+    bool over_write;
+    std::string output_path;
+    std::string file_name;
+    std::string file_extention;
 };
 
 class usage_error: public std::runtime_error {
diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index 3e9d4dd227efbb90823b99db2fedd00cc94e4f81..cf7592cd319d8cc3365f8e6465976fd0f31a3a34 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -3,20 +3,23 @@
 #include <iostream>
 #include <fstream>
 #include <memory>
+#include <vector>
 
 #include <json/src/json.hpp>
 
 #include <common_types.hpp>
 #include <cell.hpp>
 #include <cell_group.hpp>
+#include <communication/communicator.hpp>
+#include <communication/global_policy.hpp>
 #include <fvm_cell.hpp>
+#include <io/exporter_spike_file.hpp>
 #include <mechanism_catalogue.hpp>
 #include <model.hpp>
-#include <threading/threading.hpp>
 #include <profiling/profiler.hpp>
-#include <communication/communicator.hpp>
-#include <communication/global_policy.hpp>
+#include <threading/threading.hpp>
 #include <util/ioutil.hpp>
+#include <util/nop.hpp>
 #include <util/optional.hpp>
 
 #include "io.hpp"
@@ -26,15 +29,17 @@
 using namespace nest::mc;
 
 using global_policy = communication::global_policy;
-
 using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>;
 using model_type = model<lowered_cell>;
-using sample_trace_type = sample_trace<model_type::time_type, model_type::value_type>;
-
+using time_type = model_type::time_type;
+using sample_trace_type = sample_trace<time_type, model_type::value_type>;
+using file_export_type = io::exporter_spike_file<time_type, global_policy>;
 void banner();
 std::unique_ptr<recipe> make_recipe(const io::cl_options&, const probe_distribution&);
 std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe);
 std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type ncells);
+using communicator_type = communication::communicator<time_type, communication::global_policy>;
+using spike_type = typename communicator_type::spike_type;
 
 void write_trace_json(const sample_trace_type& trace, const std::string& prefix = "trace_");
 
@@ -61,9 +66,41 @@ int main(int argc, char** argv) {
         auto recipe = make_recipe(options, pdist);
         auto cell_range = distribute_cells(recipe->num_cells());
 
-        // build model from recipe
+
         model_type m(*recipe, cell_range.first, cell_range.second);
 
+        // File output is depending on the input arguments
+        std::unique_ptr<file_export_type> file_exporter;
+        std::function<void(const std::vector<spike_type>&)> do_nothing{
+            util::nop_function };
+        if (!options.spike_file_output) {
+            m.set_global_spike_callback(do_nothing);
+            m.set_local_spike_callback(do_nothing);
+        }
+        else {
+            // The exporter is the same for both global and local output
+            // just registered as a different callback
+            file_exporter =
+                util::make_unique<file_export_type>(
+                    options.file_name, options.output_path,
+                    options.file_extention, options.over_write);
+
+            if (options.single_file_per_rank) {
+                    m.set_global_spike_callback(do_nothing);
+                    m.set_local_spike_callback(
+                        [&](const std::vector<spike_type>& spikes) {
+                            file_exporter->output(spikes);
+                        });
+             }
+             else {
+                 m.set_global_spike_callback(
+                     [&](const std::vector<spike_type>& spikes) {
+                        file_exporter->output(spikes);
+                     });
+                 m.set_local_spike_callback(do_nothing);
+             }
+        }
+
         // inject some artificial spikes, 1 per 20 neurons.
         cell_gid_type spike_cell = 20*((cell_range.first+19)/20);
         for (; spike_cell<cell_range.second; spike_cell+=20) {
@@ -155,7 +192,7 @@ std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_s
     case probeKind::membrane_current:
         name = "i";
         units = "mA/cm²";
-        break; 
+        break;
     default: ;
     }
     name += probe.location.segment? "dend" : "soma";
@@ -183,5 +220,3 @@ void write_trace_json(const sample_trace_type& trace, const std::string& prefix)
     std::ofstream file(path);
     file << std::setw(1) << jrep << std::endl;
 }
-
-
diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh
index 532a7cd738d53a1a0f8342d07f09c016b5e24857..15149cddc022aa47d2f94149fc4efc6c833e9ecd 100755
--- a/nrn/generate_validation.sh
+++ b/nrn/generate_validation.sh
@@ -1,5 +1,7 @@
-python2.7 ./soma.py
-python2.7 ./ball_and_stick.py
-python2.7 ./ball_and_3stick.py
-python2.7 ./simple_synapse.py --synapse exp2
-python2.7 ./simple_synapse.py --synapse exp
+#!/usr/bin/env bash
+
+python2 ./soma.py
+python2 ./ball_and_stick.py
+python2 ./ball_and_3stick.py
+python2 ./simple_synapse.py --synapse exp2
+python2 ./simple_synapse.py --synapse exp
diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp
index 8c72430f5c4646ce7747c76cac088bee65a58df8..50ce644c2bc62904625fd74c68fe7a98de059395 100644
--- a/src/communication/communicator.hpp
+++ b/src/communication/communicator.hpp
@@ -4,6 +4,7 @@
 #include <iostream>
 #include <vector>
 #include <random>
+#include <functional>
 
 #include <spike.hpp>
 #include <util/double_buffer.hpp>
@@ -90,11 +91,15 @@ public:
     /// Returns a vector of event queues, with one queue for each local cell group. The
     /// events in each queue are all events that must be delivered to targets in that cell
     /// group as a result of the global spike exchange.
-    std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes) {
+    std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes,
+        std::function<void(const std::vector<spike_type>&)> global_export_callback)
+    {
         // global all-to-all to gather a local copy of the global spike list on each node.
         auto global_spikes = communication_policy_.gather_spikes( local_spikes );
         num_spikes_ += global_spikes.size();
 
+        global_export_callback(global_spikes);
+
         // check each global spike in turn to see it generates local events.
         // if so, make the events and insert them into the appropriate event list.
         auto queues = std::vector<event_queue>(num_groups_local());
diff --git a/src/io/exporter.hpp b/src/io/exporter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5885b7b10841593059402a491ccf21148b78df3
--- /dev/null
+++ b/src/io/exporter.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <random>
+#include <string>
+
+#include <common_types.hpp>
+#include <spike.hpp>
+
+namespace nest {
+namespace mc {
+namespace io {
+
+// interface for exporters.
+// Exposes one virtual functions:
+//    do_export(vector<type>) receiving a vector of parameters to export
+
+template <typename Time, typename CommunicationPolicy>
+class exporter {
+
+public:
+    using time_type = Time;
+    using spike_type = spike<cell_member_type, time_type>;
+
+    // Performs the export of the data
+    virtual void output(const std::vector<spike_type>&) = 0;
+
+    // Returns the status of the exporter
+    virtual bool good() const = 0;
+};
+
+} //communication
+} // namespace mc
+} // namespace nest
diff --git a/src/io/exporter_spike_file.hpp b/src/io/exporter_spike_file.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e74169d19f17090c3ca16781a5f380fc0d29cc89
--- /dev/null
+++ b/src/io/exporter_spike_file.hpp
@@ -0,0 +1,98 @@
+#pragma once
+
+#include <fstream>
+#include <iomanip>
+#include <memory>
+#include <random>
+#include <stdexcept>
+#include <vector>
+
+#include <cstring>
+#include <cstdio>
+
+#include <common_types.hpp>
+#include <io/exporter.hpp>
+#include <spike.hpp>
+#include <util.hpp>
+
+namespace nest {
+namespace mc {
+namespace io {
+
+template <typename Time, typename CommunicationPolicy>
+class exporter_spike_file : public exporter<Time, CommunicationPolicy> {
+
+public:
+    using time_type = Time;
+    using spike_type = spike<cell_member_type, time_type>;
+    using communication_policy_type = CommunicationPolicy;
+
+    // Constructor
+    // over_write if true will overwrite the specified output file (default = true)
+    // output_path  relative or absolute path
+    // file_name    will be appended with "_x" with x the rank number
+    // file_extention  a seperator will be added automatically
+    exporter_spike_file(
+        const std::string& file_name, const std::string& path,
+        const std::string& file_extention, bool over_write=true)
+    {
+        auto file_path =
+            create_output_file_path(file_name, path, file_extention,
+                communication_policy_.id());
+
+        //test if the file exist and depending on over_write throw or delete
+        if (!over_write && file_exists(file_path)) {
+            throw std::runtime_error("Tried opening file for writing but it exists and over_write is false: " +
+                file_path);
+        }
+
+        file_handle_.open(file_path);
+    }
+
+    // Performs the a export of the spikes to file
+    // one id and spike time with 4 decimals after the comma on a
+    // line space separated
+    void output(const std::vector<spike_type>& spikes) override {
+
+        for (auto spike : spikes) {
+            char linebuf[45];
+            auto n = std::snprintf(linebuf, sizeof(linebuf), "%u %.4f\n",
+                unsigned{spike.source.gid}, float(spike.time));
+            file_handle_.write(linebuf, n);
+        }
+    }
+
+    bool good() const override {
+
+        return file_handle_.good();
+    }
+
+    // Creates an indexed filename
+    static std::string create_output_file_path(
+        const std::string& file_name, const std::string& path,
+        const std::string& file_extention, unsigned index)
+    {
+        // Nest does not produce the indexing for nrank == 0
+        // I have the feeling this disrupts consistent output. Id rather
+        // always put the zero in. it allows a simpler regex when opening
+        // files
+        return path + file_name + "_" +  std::to_string(index) + "." +
+               file_extention;
+    }
+
+private:
+    bool file_exists(const std::string& file_path) {
+
+        std::ifstream fid(file_path);
+        return fid.good();
+    }
+
+    // Handle to opened file handle
+    std::ofstream file_handle_;
+
+    communication_policy_type communication_policy_;
+};
+
+} //communication
+} // namespace mc
+} // namespace nest
diff --git a/src/model.hpp b/src/model.hpp
index 84dd84298a78418ce882b68a29f2211a6e50401b..823fcd45662cfc414b7a04964bc7fb91b9b78281 100644
--- a/src/model.hpp
+++ b/src/model.hpp
@@ -1,17 +1,19 @@
 #pragma once
 
-#include <cstdlib>
+#include <memory>
 #include <vector>
 
+#include <cstdlib>
+
 #include <common_types.hpp>
 #include <cell.hpp>
 #include <cell_group.hpp>
-#include <fvm_cell.hpp>
-#include <recipe.hpp>
-#include <thread_private_spike_store.hpp>
 #include <communication/communicator.hpp>
 #include <communication/global_policy.hpp>
+#include <fvm_cell.hpp>
 #include <profiling/profiler.hpp>
+#include <recipe.hpp>
+#include <thread_private_spike_store.hpp>
 
 #include "trace_sampler.hpp"
 
@@ -26,6 +28,8 @@ public:
     using value_type = typename cell_group_type::value_type;
     using communicator_type = communication::communicator<time_type, communication::global_policy>;
     using sampler_function = typename cell_group_type::sampler_function;
+    using spike_type = typename communicator_type::spike_type;
+    using spike_export_function = std::function<void(const std::vector<spike_type>&)>;
 
     struct probe_record {
         cell_member_type id;
@@ -105,7 +109,7 @@ public:
             auto update_cells = [&] () {
                 threading::parallel_for::apply(
                     0u, cell_groups_.size(),
-                    [&](unsigned i) {
+                     [&](unsigned i) {
                         auto &group = cell_groups_[i];
 
                         PE("stepping","events");
@@ -125,10 +129,18 @@ public:
             // the previous integration period, generating the postsynaptic
             // events that must be delivered at the start of the next
             // integration period at the latest.
+
+            //TODO:
+            //An improvement might be :
+            //the exchange method simply exchanges spikes, and does not generate the event queues.It returns a struct that has both 1) the global spike list 2) an integer vector that describes the distribution of spikes across the ranks
+            //another method called something like build_queues that takes this spike info and returns the local spikes
+            //    and the callbacks can then be called on the spike information directly in the model.
             auto exchange = [&] () {
                 PE("stepping", "exchange");
                 auto local_spikes = previous_spikes().gather();
-                future_events() = communicator_.exchange(local_spikes);
+                local_export_callback_(local_spikes);
+                future_events() = communicator_.exchange(
+                    local_spikes, global_export_callback_);
                 PL(2);
             };
 
@@ -167,6 +179,20 @@ public:
     std::size_t num_spikes() const { return communicator_.num_spikes(); }
     std::size_t num_groups() const { return cell_groups_.size(); }
 
+    // register a callback that will perform a export of the global
+    // spike vector
+    void set_global_spike_callback(spike_export_function export_callback) {
+
+        global_export_callback_ = export_callback;
+    }
+
+    // register a callback that will perform a export of the rank local
+    // spike vector
+    void set_local_spike_callback(spike_export_function export_callback) {
+
+        local_export_callback_ = export_callback;
+    }
+
 private:
     cell_gid_type cell_from_;
     cell_gid_type cell_to_;
@@ -174,7 +200,6 @@ private:
     std::vector<cell_group_type> cell_groups_;
     communicator_type communicator_;
     std::vector<probe_record> probes_;
-    using spike_type = typename communicator_type::spike_type;
 
     using event_queue_type = typename communicator_type::event_queue;
     util::double_buffer< std::vector<event_queue_type> > event_queues_;
@@ -182,6 +207,9 @@ private:
     using local_spike_store_type = thread_private_spike_store<time_type>;
     util::double_buffer< local_spike_store_type > local_spikes_;
 
+    spike_export_function global_export_callback_;
+    spike_export_function local_export_callback_;
+
     // Convenience functions that map the spike buffers and event queues onto
     // the appropriate integration interval.
     //
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 10fb65efd052c1d9d7d7fdd5f50bdec0f93cb5a5..c39e22a80f408f824af43f31f1d5e965b1f37ff5 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -14,6 +14,9 @@ add_subdirectory(validation)
 # Test for the internode communication (eg. mpi)
 add_subdirectory(global_communication)
 
+# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically
+add_subdirectory(performance)
+
 
 # Proposed additional test types:
 
@@ -21,7 +24,6 @@ add_subdirectory(global_communication)
 
 # Test to check integration between components 
 
-# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically
 
 # Numbered tests based on bugs in the tracker
 
diff --git a/tests/global_communication/CMakeLists.txt b/tests/global_communication/CMakeLists.txt
index f7d000157e4cfd130399d1f718b67fbc5eb2f86d..82bb6583ea1310db1657e2a5110143fc484abd86 100644
--- a/tests/global_communication/CMakeLists.txt
+++ b/tests/global_communication/CMakeLists.txt
@@ -1 +1,31 @@
-# Nothing to be done yet
+set(HEADERS
+    ${PROJECT_SOURCE_DIR}/src/swcio.hpp
+)
+set(COMMUNICATION_SOURCES
+    test_exporter_spike_file.cpp
+    # unit test driver
+    test.cpp
+)
+
+add_executable(global_communication.exe ${COMMUNICATION_SOURCES} ${HEADERS})
+
+set(TARGETS global_communication.exe)
+
+foreach(target ${TARGETS})
+    target_link_libraries(${target} LINK_PUBLIC cellalgo gtest)
+    
+    if(WITH_TBB)
+    target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES})
+    endif()
+
+    if(WITH_MPI)
+    target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES})
+    set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}")
+    endif()
+
+    set_target_properties(${target}
+       PROPERTIES
+       RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
+    )
+endforeach()
+
diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c67a065bd88ed85aa56e865b1d4f8f9fa3d5677e
--- /dev/null
+++ b/tests/global_communication/test.cpp
@@ -0,0 +1,19 @@
+#include <iostream>
+#include <fstream>
+#include <numeric>
+#include <vector>
+
+#include "gtest.h"
+
+#include "../../src/communication/communicator.hpp"
+#include "../../src/communication/global_policy.hpp"
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    // We need to set the communicator policy at the top level
+    // this allows us to build multiple communicators in the tests
+    nest::mc::communication::global_policy_guard global_guard(argc, argv);
+
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85065a0c64b81be75a6797ffde8eb3308546210b
--- /dev/null
+++ b/tests/global_communication/test_exporter_spike_file.cpp
@@ -0,0 +1,127 @@
+#include "gtest.h"
+
+#include <cstdio>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <communication/communicator.hpp>
+#include <communication/global_policy.hpp>
+#include <communication/exporter_spike_file.hpp>
+
+class exporter_spike_file_fixture : public ::testing::Test 
+{
+protected:
+    using time_type = float;
+    using communicator_type = nest::mc::communication::global_policy;
+
+    using spike_type = 
+        nest::mc::communication::exporter_spike_file<time_type,
+        communicator_type>::spike_type;
+
+    using exporter_type = 
+        nest::mc::communication::exporter_spike_file<time_type, 
+        communicator_type>;
+    std::string file_name;
+    std::string path;
+    std::string extention;
+    unsigned index;
+
+    exporter_spike_file_fixture() :
+        file_name("spikes_exporter_spike_file_fixture"),
+        path("./"),
+        extention("gdf"),
+        index(0)
+    {}
+
+    std::string get_standard_file_name()
+    {
+        return exporter_type::create_output_file_path(
+            file_name, path, extention, 0);
+    }
+
+    void SetUp() 
+    {
+        // code here will execute just before the test ensues 
+    }
+
+    void TearDown() 
+    {
+        // delete the start create file
+        std::remove(get_standard_file_name().c_str());
+    }
+
+    ~exporter_spike_file_fixture() 
+    {}
+};
+
+TEST_F(exporter_spike_file_fixture, constructor)
+{
+    exporter_type exporter(file_name, path, extention, true);
+
+    //test if the file exist and depending on over_write throw or delete
+    std::ifstream f(get_standard_file_name());
+    
+    EXPECT_TRUE(f.good());
+
+    // We now know the file exists, so create a new exporter with overwrite false
+    try
+    {
+        exporter_type exporter1(file_name, path, extention, false);
+        FAIL() << "expected a file already exists error";
+    }
+    catch (std::runtime_error const & err)
+    {
+        EXPECT_EQ(err.what(), std::string("Tried opening file for writing but it exists and over_write is false: " +
+            get_standard_file_name()));
+    }
+    catch (...) {
+        FAIL() << "expected a file already exists error";
+    }
+}
+
+TEST_F(exporter_spike_file_fixture, create_output_file_path)
+{
+    // Create some random paths, no need for fancy tests here
+    std::string produced_filename =
+        exporter_type::create_output_file_path(
+            "spikes", "./", "gdf", 0);
+    EXPECT_STREQ(produced_filename.c_str(), "./spikes_0.gdf");
+
+    produced_filename =
+        exporter_type::create_output_file_path(
+            "a_name", "../../", "txt", 5);
+    EXPECT_STREQ(produced_filename.c_str(), "../../a_name_5.txt");
+}
+
+TEST_F(exporter_spike_file_fixture, do_export)
+{
+    {
+        exporter_type exporter(file_name, path, extention);
+
+        // Create some spikes
+        std::vector<spike_type> spikes;
+        spikes.push_back({ { 0, 0 }, 0.0 });
+        spikes.push_back({ { 0, 0 }, 0.1 });
+        spikes.push_back({ { 1, 0 }, 1.0 });
+        spikes.push_back({ { 1, 0 }, 1.1 });
+
+        // now do the export
+        exporter.do_export(spikes);
+    }  // Force destruction of exporter and explicit flush of the stream
+    // Test if we have spikes in the file?
+    std::ifstream f(get_standard_file_name());
+    EXPECT_TRUE(f.good());
+
+    std::string line;
+
+    EXPECT_TRUE(std::getline(f, line));
+    EXPECT_STREQ(line.c_str(), "0 0.0000");
+    EXPECT_TRUE(std::getline(f, line));
+    EXPECT_STREQ(line.c_str(), "0 0.1000");
+    EXPECT_TRUE(std::getline(f, line));
+    EXPECT_STREQ(line.c_str(), "1 1.0000");
+    EXPECT_TRUE(std::getline(f, line));
+    EXPECT_STREQ(line.c_str(), "1 1.1000");
+}
diff --git a/tests/performance/CMakeLists.txt b/tests/performance/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..351be926371b61bcf130d43efc42e5fb0ff53eee
--- /dev/null
+++ b/tests/performance/CMakeLists.txt
@@ -0,0 +1,2 @@
+# Unit tests
+add_subdirectory(io)
diff --git a/tests/performance/io/CMakeLists.txt b/tests/performance/io/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5d6149398089611dc161fc6591ebabe9bfc9db91
--- /dev/null
+++ b/tests/performance/io/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(HEADERS
+)
+
+set(DISK_IO_SOURCES
+    disk_io.cpp
+)
+
+add_executable(disk_io.exe ${DISK_IO_SOURCES} ${HEADERS})
+
+target_link_libraries(disk_io.exe LINK_PUBLIC cellalgo)
+
+if(WITH_MPI)
+    target_link_libraries(disk_io.exe LINK_PUBLIC ${MPI_C_LIBRARIES})
+    set_property(TARGET disk_io.exe APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}")
+endif()
+
+# Copy the python file that drives the performance tests and produces the output
+file(COPY disk_io.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..526263bf395a81eb36ed050b96cfe1923e04cd25
--- /dev/null
+++ b/tests/performance/io/disk_io.cpp
@@ -0,0 +1,160 @@
+#include <stdio.h>
+
+#include <fstream>
+#include <iostream>
+#include <numeric>
+
+#include <cell.hpp>
+#include <cell_group.hpp>
+#include <common_types.hpp>
+#include <communication/communicator.hpp>
+#include <communication/global_policy.hpp>
+#include <fvm_cell.hpp>
+#include <io/exporter_spike_file.hpp>
+#include <profiling/profiler.hpp>
+
+using namespace nest::mc;
+
+using global_policy = communication::global_policy;
+using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>;
+using cell_group_type = cell_group<lowered_cell>;
+using time_type = typename cell_group_type::time_type;
+using spike_type = io::exporter_spike_file<time_type, global_policy>::spike_type;
+using timer = util::timer_type;
+
+int main(int argc, char** argv) {
+
+    //Setup the possible mpi environment
+    communication::global_policy_guard global_guard(argc, argv);
+
+    // very simple command line parsing
+    if (argc < 3) {
+        std::cout << "disk_io <int nrspikes> <int nr_repeats>  [simple_output (false|true)]\n"
+                  << "   Simple performance test runner for the exporter manager\n"
+                  << "   It exports nrspikes nr_repeats using the export_manager and will produce\n"
+                  << "   the total, mean and std of the time needed to perform the output to disk\n\n"
+
+                  << "   <file_per_rank> true will produce a single file per mpi rank\n"
+                  << "   <simple_output> true will produce a simplyfied comma seperated output for automatic parsing\n\n"
+
+                  << "    The application can be started with mpi support and will produce output on a single rank\n"
+                  << "    if nrspikes is not a multiple of the nr of mpi rank, floor is take\n" ;
+        return 1;
+    }
+    auto nr_spikes = atoi(argv[1]);
+
+    if (nr_spikes == 0) {
+        std::cout << "disk_io <nrspikes>\n";
+        std::cout << "  nrspikes should be a valid integer higher then zero\n";
+
+        return 1;
+    }
+    auto nr_repeats = atoi(argv[2]);
+
+    if (nr_repeats == 0) {
+        std::cout << "disk_io <nrspikes>\n";
+        std::cout << "  nr_repeats should be a valid integer higher then zero\n";
+        return 1;
+    }
+
+    auto simple_stats = false;
+    if (argc == 4) {
+        std::string simple(argv[3]);
+        if (simple == std::string("true"))
+        {
+            simple_stats = true;
+        }
+    }
+
+    // Create the sut
+    io::exporter_spike_file<time_type, global_policy> exporter(
+         "spikes", "./", "gdf", true);
+
+    // We need the nr of ranks to calculate the nr of spikes to produce per
+    // rank
+    global_policy communication_policy;
+
+    auto nr_ranks = unsigned( communication_policy.size() );
+    auto spikes_per_rank = nr_spikes / nr_ranks;
+
+    // Create a set of spikes
+    std::vector<spike_type> spikes;
+
+    // *********************************************************************
+    // To have a  somewhat realworld data set we calculate from the nr of spikes
+    // (assuming 20 hz average) the number of nr of 'simulated' neurons,
+    // and create idxs using this value. The number of chars in the number
+    // influences the size of the output and thus the speed
+    // Also taken that we have only a single second of simulated time
+    // all spike times should be between 0.0 and 1.0:
+    auto simulated_neurons = spikes_per_rank / 20;
+    for (auto idx = unsigned{ 0 }; idx < spikes_per_rank; ++idx) {
+
+        spikes.push_back({
+            {idx % simulated_neurons, 0 },   // correct idx
+            0.0f + 1 / (0.05f + idx % 20)
+        });  // semi random float
+    }
+
+    double timings_arr[nr_repeats];
+    double time_total = 0;
+
+    // now output to disk nr_repeats times, while keeping track of the times
+    for (auto idx = 0; idx < nr_repeats; ++idx) {
+        auto time_start = timer::tic();
+        exporter.output(spikes);
+        auto run_time = timer::toc(time_start);
+
+        time_total += run_time;
+        timings_arr[idx] = run_time;
+    }
+
+    // create the vector here to prevent changes on the heap influencing the
+    // timeing
+    std::vector<double> timings;
+    for (auto idx = 0; idx < nr_repeats; ++idx) {
+        timings.push_back(timings_arr[idx]);
+    }
+
+
+    // Calculate some statistics
+    auto sum = std::accumulate(timings.begin(), timings.end(), 0.0);
+    auto mean = sum / timings.size();
+
+    std::vector<double> diff(timings.size());
+    std::transform(
+        timings.begin(), timings.end(), diff.begin(),
+        std::bind2nd(std::minus<double>(), mean)
+    );
+    auto sq_sum = std::inner_product(
+        diff.begin(), diff.end(), diff.begin(),
+        0.0
+    );
+    auto stdev = std::sqrt(sq_sum / timings.size());
+
+    auto min = *std::min_element(timings.begin(), timings.end());
+    auto max = *std::max_element(timings.begin(), timings.end());
+
+
+    if (communication_policy.id() != 0) {
+        return 0;
+    }
+
+    // and output
+    if (simple_stats) {
+        std::cout << time_total<< ","
+                  << mean  << ","
+                  << stdev << ","
+                  << min << ","
+                  << max << std::endl;
+    }
+    else {
+        std::cout << "total time (ms): " << time_total  <<  std::endl;
+        std::cout << "mean  time (ms): " << mean <<  std::endl;
+        std::cout << "stdev  time (ms): " <<  std::endl;
+        std::cout << "min  time (ms): " << min << std::endl;
+        std::cout << "max  time (ms): " << max << std::endl;
+    }
+
+    return 0;
+}
diff --git a/tests/performance/io/disk_io.py b/tests/performance/io/disk_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea42304b23e2da851058b8b6d7625f8185ec0db3
--- /dev/null
+++ b/tests/performance/io/disk_io.py
@@ -0,0 +1,49 @@
+import matplotlib.pyplot as plt
+import subprocess
+import os
+
+
+
+current_script_dir = os.path.dirname(os.path.abspath(__file__))
+
+spikes_to_save = 1000000
+
+print ( "Simple performance runner for spike output to file. \n" +
+        str(spikes_to_save) + " spikes will be written to a file and the duration of this \n" +
+        "operation measured for different number of ranks\n" )
+
+
+range_nr_rank = [1, 2, 4, 8, 16, 24, 32, 48, 64]
+mean = []
+std = []
+min = []
+max = []
+for n_rank in range_nr_rank:
+    # open the disk_io executable
+    p1 = subprocess.Popen(["mpirun", "-n",str(n_rank),
+                           os.path.join(current_script_dir, "disk_io.exe"),
+                           str(spikes_to_save), str(10), "true"],
+                          stdout=subprocess.PIPE)
+    
+    #and grab the raw stats
+    stats =  p1.communicate()[0]
+
+    # convert into list
+    stats = stats.split(",")
+
+    mean.append(float(stats[1]))
+    std.append(float(stats[2]))
+    min.append(float(stats[3]))
+    max.append(float(stats[4]))
+
+    print ("performed test for n_rank= " + str(n_rank))
+
+print (range_nr_rank)
+print (mean)
+print (std)
+
+plt.errorbar(range_nr_rank, mean, yerr=std, fmt='-o', label="mean (std)")
+plt.errorbar(range_nr_rank, min, fmt='-', label="min")
+plt.errorbar(range_nr_rank, max, fmt='-', label="max")
+plt.legend()
+plt.show()