Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/test_windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI / Test (Windows)

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

on:
pull_request:
branches: [master]
workflow_dispatch:

jobs:
test-msvc:
name: Visual Studio 2022 / x64 / AVX2
runs-on: windows-2022
timeout-minutes: 45

steps:
- uses: actions/checkout@v4
- name: Configure
run: cmake -S . -B build -A x64 -DENABLE_ASAN=OFF
- name: Build
run: cmake --build build --config Release --target unittest --parallel
- name: Test
run: ctest --test-dir build -C Release --output-on-failure
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
sonic_cpp
Expand All @@ -15,6 +16,7 @@ option(ENABLE_RISCV_ZBB "Enable RISC-V Zbb extension for bit manipulation optimi

set(CMAKE_CXX_EXTENSIONS OFF)
if(BUILD_UNITTEST)
enable_testing()
add_subdirectory(tests)
endif()

Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ A fast JSON serializing & deserializing library, accelerated by SIMD.

[![clang-format Check](https://github.com/bytedance/sonic-cpp/actions/workflows/clang-format-check.yml/badge.svg)](https://github.com/bytedance/sonic-cpp/actions/workflows/clang-format-check.yml)
[![Test](https://github.com/bytedance/sonic-cpp/actions/workflows/test_x86.yml/badge.svg)](https://github.com/bytedance/sonic-cpp/actions/workflows/test_x86.yml)
[![Windows](https://github.com/bytedance/sonic-cpp/actions/workflows/test_windows.yml/badge.svg)](https://github.com/bytedance/sonic-cpp/actions/workflows/test_windows.yml)
[![codecov](https://codecov.io/gh/bytedance/sonic-cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/bytedance/sonic-cpp)

## Requirement
- c++11 or above
- x86 platform with AVX2 instruction
- GCC or LLVM compiler (not support MSVC compiler now)
- Linux OS
- C++17 or above
- A supported SIMD target (x86-64 with AVX2, AArch64, or RV64)
- GCC, LLVM/Clang, or Microsoft Visual C++ from Visual Studio 2022
- Linux, macOS, or 64-bit Windows

### Visual Studio 2022

The CMake build selects `/std:c++17` and `/arch:AVX2` for MSVC automatically:

```powershell
cmake -S . -B build -A x64 -DENABLE_ASAN=OFF
cmake --build build --config Release --target unittest --parallel
ctest --test-dir build -C Release --output-on-failure
```

When consuming the header-only library from a Visual Studio project directly,
enable C++17 and **Advanced Vector Extensions 2** (`/arch:AVX2`).

## Features
- Complete APIs for JSON value manipulation
Expand Down
14 changes: 10 additions & 4 deletions cmake/set_arch_flags.cmake
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
function(set_arch_flags target arch)
message(STATUS "Setting architecture flags for ${arch}")
if(arch MATCHES "x86_64")
target_compile_options(${target} PRIVATE -mavx2 -mpclmul -mbmi -mlzcnt)
elseif(arch MATCHES "aarch64|arm64")
string(TOLOWER "${arch}" arch_lower)
if(arch_lower MATCHES "x86_64|amd64|x64")
if(MSVC)
target_compile_options(${target} PRIVATE /arch:AVX2 /permissive-
/Zc:__cplusplus /Zc:preprocessor)
else()
target_compile_options(${target} PRIVATE -mavx2 -mpclmul -mbmi -mlzcnt)
endif()
elseif(arch_lower MATCHES "aarch64|arm64")
if(APPLE)
target_compile_options(${target} PRIVATE -mcpu=apple-m1)
else()
Expand All @@ -12,7 +18,7 @@ function(set_arch_flags target arch)
target_compile_options(${target} PRIVATE -march=armv8-a)
endif()
endif()
elseif(arch MATCHES "riscv64|riscv")
elseif(arch_lower MATCHES "riscv64|riscv")
if(ENABLE_RISCV_ZBB)
target_compile_options(${target} PRIVATE -march=rv64gc_zbb)
else()
Expand Down
4 changes: 3 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Examples of how to use Sonic-Cpp.

## Build with arch option
Sonic-Cpp is a header-only library, you only need to add `-mavx2 -mpclmul -mbmi`
or `-march=haswell` to support.
or `-march=haswell` with GCC/Clang. With Visual Studio 2022, compile for x64
with `/std:c++17 /arch:AVX2`; the repository CMake build adds these options
automatically.

## Basic Usage
### Parse and Serialize
Expand Down
12 changes: 3 additions & 9 deletions include/sonic/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <atomic>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mutex>
Expand Down Expand Up @@ -88,14 +89,7 @@ class SpinLock {
// use pause or yield instruction will slow down lock acquisition
// on contended locks.
#ifndef SONIC_SPINLOCK_NO_PAUSE

#if defined(__x86_64__) || defined(_M_AMD64)
__builtin_ia32_pause();
#elif defined(__aarch64__) || defined(_M_ARM64)
asm volatile("yield");
#elif defined(__riscv)
__asm__ volatile("pause");
#endif
internal::CpuRelax();
#endif
}
}
Expand Down Expand Up @@ -157,7 +151,7 @@ class AdaptiveChunkPolicy {
if (min_chunk_size_ < need_alloc_size &&
min_chunk_size_ < SONIC_ALLOCATOR_MAX_CHUNK_CAPACITY) {
size_t p =
1ULL << (64 - __builtin_clzll(
1ULL << (64 - internal::CountLeadingZeros64(
need_alloc_size)); // size > 0 && never shift 64
min_chunk_size_ = p < SONIC_ALLOCATOR_MAX_CHUNK_CAPACITY
? p
Expand Down
9 changes: 5 additions & 4 deletions include/sonic/dom/serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma once

#include <cmath>
#include <cstddef>
#include <cstring>

#include "sonic/dom/flags.h"
Expand Down Expand Up @@ -51,9 +52,9 @@ sonic_force_inline SonicError SerializeImpl(const NodeType* node,
uint32_t member_cnt = 0;
uint32_t val_cnt, val_cnt_nxt;
size_t str_len;
long inc_len;
size_t inc_len;
const char* str_ptr;
ssize_t rn = 0;
std::ptrdiff_t rn = 0;
internal::Stack stk;
ParentCtx* parent;
if constexpr ((serializeFlags & SerializeFlags::kSerializeAppendBuffer) ==
Expand Down Expand Up @@ -84,7 +85,7 @@ sonic_force_inline SonicError SerializeImpl(const NodeType* node,
str_ptr = node->GetStringView().data();
rn = internal::Quote<serializeFlags>(str_ptr, str_len, wb.End<char>()) -
wb.End<char>();
wb.PushSizeUnsafe<char>(rn);
wb.PushSizeUnsafe<char>(static_cast<size_t>(rn));
wb.PushUnsafe<char>(is_key ? ':' : ',');
member_cnt -= is_key;
break;
Expand Down Expand Up @@ -138,7 +139,7 @@ sonic_force_inline SonicError SerializeImpl(const NodeType* node,
break;
}
sonic_assert(rn >= 0 && rn <= 32);
wb.PushSizeUnsafe<char>(rn);
wb.PushSizeUnsafe<char>(static_cast<size_t>(rn));
wb.PushUnsafe<char>(',');
break;
}
Expand Down
44 changes: 19 additions & 25 deletions include/sonic/internal/arch/avx2/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ sonic_force_inline int TrailingZeroes(uint64_t input_num) {
// return (int)_tzcnt_u64(input_num);
// but the generated code differs and might be less efficient?
////////
return __builtin_ctzll(input_num);
return CountTrailingZeros64(input_num);
}

/* result might be undefined when input_num is zero */
sonic_force_inline uint64_t ClearLowestBit(uint64_t input_num) {
#if __BMI__
#if defined(__BMI__)
return _blsr_u64(input_num);
#else
return input_num & (input_num - 1);
Expand All @@ -77,17 +77,17 @@ sonic_force_inline uint64_t ClearLowestBit(uint64_t input_num) {

/* result might be undefined when input_num is zero */
sonic_force_inline int LeadingZeroes(uint64_t input_num) {
return __builtin_clzll(input_num);
return CountLeadingZeros64(input_num);
}

sonic_force_inline long long int CountOnes(uint64_t input_num) {
return __builtin_popcountll(input_num);
return CountOnes64(input_num);
}

sonic_force_inline uint64_t PrefixXor(const uint64_t bitmask) {
// There should be no such thing with a processor supporting avx2
// but not clmul.
#if __PCLMUL__
#if defined(__PCLMUL__) || defined(_MSC_VER)
__m128i all_ones = _mm_set1_epi8('\xFF');
__m128i result =
_mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0);
Expand Down Expand Up @@ -217,13 +217,10 @@ static sonic_force_inline int cmp_lt_32(const void* _l, const void* _r,
#pragma GCC diagnostic pop
#endif
__m256i ans = _mm256_cmpeq_epi8(vec_l, vec_r);
int mask = _mm256_movemask_epi8(ans) + 1;
// mask = mask << (32 -s);
__asm__("bzhil %1, %2, %[result]\n\t"
: [result] "=r"(mask)
: "r"((int)s), "r"(mask));
uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(ans)) + 1;
mask &= (uint32_t{1} << s) - 1;
if (mask) {
int ne_idx = __builtin_ctz(mask);
int ne_idx = CountTrailingZeros32(mask);
// if (lhs[ne_idx] < rhs[ne_idx]) return -1;
// else return 1;
return lhs[ne_idx] - rhs[ne_idx];
Expand Down Expand Up @@ -254,14 +251,14 @@ static inline bool is_eq_lt_32_cross_page(const void* _a, const void* _b,
}
// cross page
if (s >= 8) {
return __builtin_memcmp(a, b, 8) == 0 &&
__builtin_memcmp(a + s - 8, b + s - 8, 8) == 0;
return std::memcmp(a, b, 8) == 0 &&
std::memcmp(a + s - 8, b + s - 8, 8) == 0;
} else if (s >= 4) {
return __builtin_memcmp(a, b, 4) == 0 &&
__builtin_memcmp(a + s - 4, b + s - 4, 4) == 0;
return std::memcmp(a, b, 4) == 0 &&
std::memcmp(a + s - 4, b + s - 4, 4) == 0;
} else if (s >= 2) {
return __builtin_memcmp(a, b, 2) == 0 &&
__builtin_memcmp(a + s - 2, b + s - 2, 2) == 0;
return std::memcmp(a, b, 2) == 0 &&
std::memcmp(a + s - 2, b + s - 2, 2) == 0;
} else {
return *a == *b;
}
Expand All @@ -276,11 +273,8 @@ static sonic_force_inline bool is_eq_lt_32(const void* _a, const void* _b,
__m256i vec_a = _mm256_loadu_si256((__m256i const*)a);
__m256i vec_b = _mm256_loadu_si256((__m256i const*)b);
__m256i ans = _mm256_cmpeq_epi8(vec_a, vec_b);
int mask = _mm256_movemask_epi8(ans) + 1;
// mask = mask << (32 -s);
__asm__("bzhil %1, %2, %[result]\n\t"
: [result] "=r"(mask)
: "r"((int)s), "r"(mask));
uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(ans)) + 1;
mask &= (uint32_t{1} << s) - 1;
return mask == 0;
}
return is_eq_lt_32_cross_page(a, b, s);
Expand Down Expand Up @@ -334,7 +328,7 @@ sonic_force_inline int InlinedMemcmp(const void* _l, const void* _r, size_t s) {
__m256i ans_1 = _mm256_cmpeq_epi8(vec_l, vec_r);
uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(ans_1)) + 1;
if (mask) {
int ne_idx = __builtin_ctz(mask);
int ne_idx = CountTrailingZeros32(mask);
// if (lhs[ne_idx] < rhs[ne_idx]) return -1;
// else return 1;
return lhs[ne_idx] - rhs[ne_idx];
Expand All @@ -346,7 +340,7 @@ sonic_force_inline int InlinedMemcmp(const void* _l, const void* _r, size_t s) {
__m256i ans = _mm256_cmpeq_epi8(vec_l, vec_r);
mask = static_cast<uint32_t>(_mm256_movemask_epi8(ans)) + 1;
if (mask) {
int ne_idx = __builtin_ctz(mask);
int ne_idx = CountTrailingZeros32(mask);
// if (lhs[i + ne_idx] < rhs[i + ne_idx]) return -1;
// else return 1;
return lhs[i + ne_idx] - rhs[i + ne_idx];
Expand All @@ -363,7 +357,7 @@ sonic_force_inline int InlinedMemcmp(const void* _l, const void* _r, size_t s) {
// ans = _mm256_and_si256(ans, ans_1);
unsigned int mask = static_cast<uint32_t>(_mm256_movemask_epi8(ans)) + 1;
if (mask) {
int ne_idx = __builtin_ctz(mask);
int ne_idx = CountTrailingZeros32(mask);
return lhs[offset + ne_idx] - rhs[offset + ne_idx];
}
}
Expand Down
Loading