For learning purposes, I decided to implement my own vector data structure. I called it list because that seems to generally be the more proper name for it but that's unimportant.
I am halfway through implementing this class (inserting and getting are complete) and I decide to write some benchmarks with surprising results.
My compiler is whatever Visual Studio 2019 uses. I have tried debug and release, in x64 and x86.
For some reason, my implementation is faster than vector and I cannot think of a reason why. I fear that either my implementation or testing method are flawed.
Here are my results (x64, debug):
List: 13269ms
Vector: 78515ms
Release has a much less drastic, but still apparent, difference.
List: 65ms
Vector: 247ms
Here is my code
dataset.hpp:
#ifndef DATASET_H
#define DATASET_H
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <functional>
#include <chrono>
namespace Dataset {
template <class T>
class List {
public:
List();
List(unsigned int);
void push(T);
T& get(int);
void reserve(int);
void shrink();
int count();
int capacity();
~List();
private:
void checkCapacity(int);
void setCapacity(int);
char* buffer;
int mCount, mCapacity;
};
template <class T>
List<T>::List() {
mCount = 0;
mCapacity = 0;
buffer = 0;
setCapacity(64);
}
template <class T>
List<T>::List(unsigned int initcap) {
mCount = 0;
buffer = 0;
setCapacity(initcap);
}
template <class T>
void List<T>::push(T item) {
checkCapacity(1);
new(buffer + (sizeof(T) * mCount++)) T(item);
}
template <class T>
T& List<T>::get(int index) {
return *((T*)(buffer + (sizeof(T) * index)));
}
template <class T>
void List<T>::reserve(int desired) {
if (desired > mCapacity) {
setCapacity(desired);
}
}
template <class T>
void List<T>::shrink() {
if (mCapacity > mCount) {
setCapacity(mCount);
}
}
template <class T>
int List<T>::count() {
return mCount;
}
template <class T>
int List<T>::capacity() {
return mCapacity;
}
template <class T>
void List<T>::checkCapacity(int cap) {
// Can <cap> more items fit in the list? If not, expand!
if (mCount + cap > mCapacity) {
setCapacity((int)((float)mCapacity * 1.5));
}
}
template <class T>
void List<T>::setCapacity(int cap) {
mCapacity = cap;
// Does buffer exist yet?
if (!buffer) {
// Allocate a new buffer
buffer = new char[sizeof(T) * cap];
}
else {
// Reallocate the old buffer
char* newBuffer = new char[sizeof(T) * cap];
if (newBuffer) {
std::copy(buffer, buffer + (sizeof(T) * mCount), newBuffer);
delete[] buffer;
buffer = newBuffer;
}
else {
throw std::runtime_error("Allocation failed");
}
}
}
template <class T>
List<T>::~List() {
for (int i = 0; i < mCount; i++) {
get(i).~T();
}
delete[] buffer;
}
long benchmark(std::function<void()>);
long benchmark(std::function<void()>, long);
long benchmark(std::function<void()> f) {
return benchmark(f, 100000);
}
long benchmark(std::function<void()> f, long iters) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
auto start = high_resolution_clock::now();
for (long i = 0; i < iters; i++) {
f();
}
auto end = high_resolution_clock::now();
auto time = duration_cast<std::chrono::milliseconds>(end - start);
return (long)time.count();
}
}
#endif
test.cpp:
#include "dataset.hpp"
#include <iostream>
#include <vector>
/*
TEST CODE
*/
class SimpleClass {
public:
SimpleClass();
SimpleClass(int);
SimpleClass(const SimpleClass&);
void sayHello();
~SimpleClass();
private:
int data;
};
SimpleClass::SimpleClass() {
//std::cout << "Constructed " << this << std::endl;
data = 0;
}
SimpleClass::SimpleClass(int data) {
//std::cout << "Constructed " << this << std::endl;
this->data = data;
}
SimpleClass::SimpleClass(const SimpleClass& other) {
//std::cout << "Copied to " << this << std::endl;
data = other.data;
}
SimpleClass::~SimpleClass() {
//std::cout << "Deconstructed " << this << std::endl;
}
void SimpleClass::sayHello() {
std::cout << "Hello! I am #" << data << std::endl;
}
int main() {
long list = Dataset::benchmark([]() {
Dataset::List<SimpleClass> list = Dataset::List<SimpleClass>(1000);
for (int i = 0; i < 1000; i++) {
list.push(SimpleClass(i));
}
});
long vec = Dataset::benchmark([]() {
std::vector<SimpleClass> list = std::vector<SimpleClass>(1000);
for (int i = 0; i < 1000; i++) {
list.emplace_back(SimpleClass(i));
}
});
std::cout << "List: " << list << "ms" << std::endl;
std::cout << "Vector: " << vec << "ms" << std::endl;
return 0;
}