Skip to content

idealvin/coost

 
 

Repository files navigation

Coost

English | 简体中文

Linux Build Windows Build Mac Build Release License: MIT

A tiny boost library in C++11.

0. Introduction

coost is an elegant and efficient cross-platform C++ base library. Its goal is to create a sword of C++ to make C++ programming easy and enjoyable.

Coost, co for short, is like boost, but more lightweight, the static library built on linux or mac is only about 1MB in size. However, it still provides enough powerful features:

  • Command line and config file parser (flag)
  • High performance log library (log)
  • Unit testing framework
  • Bechmark testing framework
  • go-style coroutine
  • Coroutine-based network library
  • JSON RPC framework
  • Atomic operation (atomic)
  • Efficient stream (fastream)
  • Efficient string (fastring)
  • String utility (str)
  • Time library (time)
  • Thread library (thread)
  • Timed Task Scheduler
  • God-oriented programming
  • Efficient JSON library
  • Hash library
  • Path library
  • File utilities (fs)
  • System operations (os)
  • Fast memory allocator

1. Sponsor

Coost needs your help. If you are using it or like it, you may consider becoming a sponsor. Thank you very much!

2. Documents

3. Core features

3.0 God-oriented programming

co/god.h provides some features based on templates.

#include "co/god.h"

void f() {
    god::bless_no_bugs();
    god::is_same<T, int, bool>(); // T is int or bool?
}

3.1 flag

flag is a command line and config file parser. It is similar to gflags, but more powerful:

  • Support parameters from both command-line and config file.
  • Support automatic generation of the config file.
  • Support flag aliases.
  • Flag of integer type, the value can take a unit k,m,g,t,p.
#include "co/flag.h"
#include "co/cout.h"

DEF_bool(x, false, "x");
DEF_bool(y, true, "y");
DEF_bool(debug, false, "dbg", d);
DEF_uint32(u, 0, "xxx");
DEF_string(s, "", "xx");

int main(int argc, char** argv) {
    flag::parse(argc, argv);
    cout << "x: " << FLG_x << '\n';
    cout << "y: " << FLG_y << '\n';
    cout << "debug: " << FLG_debug << '\n';
    cout << "u: " << FLG_u << '\n';
    cout << FLG_s << "|" << FLG_s.size() << '\n';
    return 0;
}

In the above example, the macros start with DEF_ define 4 flags. Each flag corresponds to a global variable, whose name is FLG_ plus the flag name. The flag debug has an alias d. After building, the above code can run as follow:

./xx                  # Run with default configs
./xx -x -s good       # x -> true, s -> "good"
./xx -debug           # debug -> true
./xx -xd              # x -> true, debug -> true
./xx -u 8k            # u -> 8192

./xx -mkconf          # Automatically generate a config file: xx.conf
./xx xx.conf          # run with a config file
./xx -conf xx.conf    # Same as above

3.2 log

log is a high-performance log library, some components in coost use it to print logs.

log supports two types of logs: one is level log, which is divided into 5 levels: debug, info, warning, error and fatal, printing a fatal log will terminate the program; the other is topic log, logs are grouped by topic, and logs of different topics are written to different files.

#include "co/log.h"

int main(int argc, char** argv) {
    flag::parse(argc, argv);

    TLOG("xx") << "s" << 23; // topic log
    DLOG << "hello " << 23;  // debug
    LOG << "hello " << 23;   // info
    WLOG << "hello " << 23;  // warning
    ELOG << "hello " << 23;  // error
    FLOG << "hello " << 23;  // fatal

    return 0;
}

co/log also provides a series of CHECK macros, which is an enhanced version of assert, and they will not be cleared in debug mode.

void* p = malloc(32);
CHECK(p != NULL) << "malloc failed..";
CHECK_NE(p, NULL) << "malloc failed..";

log is very fast, the following are some test results:

platform glog co/log speedup
win2012 HHD 1.6MB/s 180MB/s 112.5
win10 SSD 3.7MB/s 560MB/s 151.3
mac SSD 17MB/s 450MB/s 26.4
linux SSD 54MB/s 1023MB/s 18.9

The above is the write speed of co/log and glog (single thread, 1 million logs). It can be seen that co/log is nearly two orders of magnitude faster than glog.

threads linux co/log linux spdlog win co/log win spdlog speedup
1 0.087235 2.076172 0.117704 0.461156 23.8/3.9
2 0.183160 3.729386 0.158122 0.511769 20.3/3.2
4 0.206712 4.764238 0.316607 0.743227 23.0/2.3
8 0.302088 3.963644 0.406025 1.417387 13.1/3.5

The above is the time of printing 1 million logs with 1, 2, 4, and 8 threads, in seconds. Speedup is the performance improvement of co/log compared to spdlog on linux and windows platforms.

3.3 unitest

unitest is a simple and easy-to-use unit test framework. Many components in coost use it to write unit test code, which guarantees the stability of coost.

#include "co/unitest.h"
#include "co/os.h"

DEF_test(os) {
    DEF_case(homedir) {
        EXPECT_NE(os::homedir(), "");
    }

    DEF_case(cpunum) {
        EXPECT_GT(os::cpunum(), 0);
    }
}
 
int main(int argc, char** argv) {
    flag::parse(argc, argv);
    unitest::run_tests();
    return 0;
}

The above is a simple example. The DEF_test macro defines a test unit, which is actually a function (a method in a class). The DEF_case macro defines test cases, and each test case is actually a code block.

The directory unitest contains the unit test code in coost. Users can run unitest with the following commands:

xmake r unitest      # Run all test cases
xmake r unitest -os  # Run test cases in the os unit

3.4 JSON

In coost v3.0, Json provides fluent APIs, which is more convenient to use.

// {"a":23,"b":false,"s":"123","v":[1,2,3],"o":{"xx":0}}
co::Json x = {
    { "a", 23 },
    { "b", false },
    { "s", "123" },
    { "v", {1,2,3} },
    { "o", {
        {"xx", 0}
    }},
};

// equal to x
co::Json y = Json()
    .add_member("a", 23)
    .add_member("b", false)
    .add_member("s", "123")
    .add_member("v", Json().push_back(1).push_back(2).push_back(3))
    .add_member("o", Json().add_member("xx", 0));

x.get("a").as_int();       // 23
x.get("s").as_string();    // "123"
x.get("s").as_int();       // 123, string -> int
x.get("v", 0).as_int();    // 1
x.get("v", 2).as_int();    // 3
x.get("o", "xx").as_int(); // 0

x["a"] == 23;          // true
x["s"] == "123";       // true
x.get("o", "xx") != 0; // false
os co/json stringify co/json parse rapidjson stringify rapidjson parse speedup
win 569 924 2089 2495 3.6/2.7
mac 783 1097 1289 1658 1.6/1.5
linux 468 764 1359 1070 2.9/1.4

The above is the average time of stringifying and parsing minimized twitter.json, in microseconds (us), speedup is the performance improvement of co/json compared to rapidjson.

3.5 Coroutine

coost has implemented a go-style coroutine, which has the following features:

  • Support multi-thread scheduling, the default number of threads is the number of system CPU cores.
  • Shared stack, coroutines in the same thread share several stacks (the default size is 1MB), and the memory usage is low.
  • There is a flat relationship between coroutines, and new coroutines can be created from anywhere (including in coroutines).
  • Support coroutine synchronization events, coroutine locks, channels, and waitgroups.
#include "co/co.h"

int main(int argc, char** argv) {
    flag::parse(argc, argv);

    co::wait_group wg;
    wg.add(2);

    go([wg](){
        LOG << "hello world";
        wg.done();
    });

    go([wg](){
        LOG << "hello again";
        wg.done();
    });

    wg.wait();
    return 0;
}

In the above code, the coroutines created by go() will be distributed to different scheduling threads. Users can also control the scheduling of coroutines by themselves:

// run f1 and f2 in the same scheduler
auto s = co::next_sched();
s->go(f1);
s->go(f2);

// run f in all schedulers
for (auto& s : co::scheds()) {
    s->go(f);
}

3.6 network programming

coost provides a coroutine-based network programming framework:

  • coroutineized socket API, similar in form to the system socket API, users familiar with socket programming can easily write high-performance network programs in a synchronous manner.
  • TCP, HTTP, RPC and other high-level network programming components, compatible with IPv6, also support SSL, it is more convenient to use than socket API.

RPC server

#include "co/co.h"
#include "co/rpc.h"
#include "co/time.h"

int main(int argc, char** argv) {
    flag::parse(argc, argv);

    rpc::Server()
        .add_service(new xx::HelloWorldImpl)
        .start("127.0.0.1", 7788, "/xx");

    for (;;) sleep::sec(80000);
    return 0;
}

rpc::Server also supports HTTP protocol, you may use the POST method to call the RPC service:

curl http://127.0.0.1:7788/xx --request POST --data '{"api":"ping"}'

Static web server

#include "co/flag.h"
#include "co/http.h"

DEF_string(d, ".", "root dir"); // docroot for the web server

int main(int argc, char** argv) {
    flag::parse(argc, argv);
    so::easy(FLG_d.c_str()); // mum never have to worry again
    return 0;
}

HTTP server

void cb(const http::Req& req, http::Res& res) {
    if (req.is_method_get()) {
        if (req.url() == "/hello") {
            res.set_status(200);
            res.set_body("hello world");
        } else {
            res.set_status(404);
        }
    } else {
        res.set_status(405); // method not allowed
    }
}

// http
http::Server().on_req(cb).start("0.0.0.0", 80);

// https
http::Server().on_req(cb).start(
    "0.0.0.0", 443, "privkey.pem", "certificate.pem"
);

HTTP client

void f() {
    http::Client c("https://github.com");

    c.get("/");
    LOG << "response code: "<< c.status();
    LOG << "body size: "<< c.body().size();
    LOG << "Content-Length: "<< c.header("Content-Length");
    LOG << c.header();

    c.post("/hello", "data xxx");
    LOG << "response code: "<< c.status();
}

go(f);

4. Code composition

  • include

    Header files of coost.

  • src

    Source files of coost, built as libco.

  • test

    Test code, each .cc file will be compiled into a separate test program.

  • unitest

    Unit test code, each .cc file corresponds to a different test unit, and all code will be compiled into a single test program.

  • gen

    A code generator for the RPC framework.

5. Building

5.1 Compilers required

To build coost, you need a compiler that supports C++11:

5.2 Build with xmake

coost recommends using xmake as the build tool.

5.2.1 Quick start

# All commands are executed in the root directory of coost (the same below)
xmake      # build libco by default
xmake -a   # build all projects (libco, gen, test, unitest)

5.2.2 Build shared library

xmake f -k shared
xmake -v

5.2.3 Build with mingw

xmake f -p mingw
xmake -v

5.2.4 Enable HTTP/SSL features

xmake f --with_libcurl=true --with_openssl=true
xmake -v

5.2.5 Install libco

# Install header files and libco by default.
xmake install -o pkg         # package related files to the pkg directory
xmake i -o pkg               # the same as above
xmake install -o /usr/local  # install to the /usr/local directory

5.2.6 Install libco from xrepo

xrepo install -f "openssl=true,libcurl=true" coost

5.3 Build with cmake

izhengfan helped to provide cmake support, SpaceIm improved it and made it perfect.

master

get twitter.json

wget https://raw.githubusercontent.com/simdjson/simdjson/master/jsonexamples/twitter.json

<<<<<<< HEAD

build json benchmark

xmake -b json_bm

copy twitter.json to the release dir

cp twitter.json build/windows/x64/release/twitter.json cp twitter.json build/linux/x86_64/release/twitter.json

run benchmark with twitter.json

xmake r json_bm

run benchmark with minimal twitter.json

xmake r json_bm -minimal

=======
#### 5.3.1 Build libco

```sh
mkdir build && cd build
cmake ..
make -j8

5.3.2 Build all projects

mkdir build && cd build
cmake .. -DBUILD_ALL=ON
make -j8

master

  • results on linux
    parse stringify parse(minimal) stringify(minimal)
    rapidjson 1233.701 us 2503 us 1057.799 us 2243 us
    simdjson 385.3 us 1779 us 351.752 us 2298 us
    co/json 666.979 us 1660 us 457.381 us 981 us

<<<<<<< HEAD

  • results on windows
    parse stringify parse(minimal) stringify(minimal)
    rapidjson 4197.339 us 3008 us 4078.067 us 2216 us
    simdjson 843.06 us 2373 us 607.946 us 2119 us
    co/json 717.444 us 1514 us 623.745 us 993 us

=======

5.3.3 Enable HTTP/SSL features

mkdir build && cd build
cmake .. -DWITH_LIBCURL=ON -DWITH_OPENSSL=ON
make -j8

5.3.4 Build shared library

cmake .. -DBUILD_SHARED_LIBS=ON
make -j8

5.3.5 Find coost in Cmake

find_package(coost REQUIRED CONFIG)
target_link_libraries(userTarget coost::co)

5.3.6 vcpkg & conan

vcpkg install coost:x64-windows

# HTTP & SSL support
vcpkg install coost[libcurl,openssl]:x64-windows

conan install coost

6. License

The MIT license. coost contains codes from some other projects, which have their own licenses, see details in LICENSE.md.

7. Special thanks

  • The code of co/context is from tbox by ruki, special thanks!
  • The early English documents of co are translated by Leedehai and daidai21, special thanks!
  • ruki has helped to improve the xmake building scripts, thanks in particular!
  • izhengfan provided cmake building scripts, thank you very much!
  • SpaceIm has improved the cmake building scripts, and provided support for find_package. Really great help, thank you!