Skip to main content

A Rust-powered NumPy-compatible array library.

Project description

rsnumpy

A Rust-powered NumPy-compatible array library.

Build Status License Python Version Rust Version


中文文档

1. 项目简介

rsnumpy 是一个由 Rust 驱动的高性能多维数组库,提供与 NumPy 兼容的 API。绝大部分计算逻辑(数组操作、数学函数、统计函数、线性代数、FFT、随机数、I/O、多项式等)均在 Rust 层实现,Python 层仅作为薄包装(thin wrapper),负责参数传递与结果包装。

优势:

  • 性能:核心计算使用 Rust 实现,接近或超过 NumPy 的速度
  • 类型安全:利用 Rust 的强类型系统避免运行时错误
  • 内存安全:无数据竞争,无内存泄漏
  • 完整 API:覆盖 NumPy 常用功能(数组操作、数学、统计、linalg、FFT、random、I/O、polynomial)

项目结构:

rsnumpy/
├── src/                       # Rust 源码
│   ├── lib.rs                 # 核心 ndarray 与通用函数
│   ├── fft.rs                 # 快速傅里叶变换
│   ├── linalg.rs              # 线性代数
│   └── random.rs              # 随机数生成
├── rsnumpy/                     # Python 薄包装
│   ├── __init__.py            # 主模块,整合所有 API
│   ├── array_methods.py       # ndarray 对象方法
│   ├── array_ops.py           # 数组操作函数
│   ├── math_functions.py      # 数学函数
│   ├── statistics.py          # 统计函数
│   ├── io.py                  # 文件 I/O
│   ├── linalg/                # 线性代数子模块
│   ├── polynomial/            # 多项式子模块
│   └── random/                # 随机数子模块
├── Cargo.toml                 # Rust 依赖
├── pyproject.toml             # Python 构建配置
├── build_wheel.sh             # 构建脚本
└── README.md                  # 本文件

2. 环境要求

工具 最低版本 说明
Python ≥ 3.10 推荐 3.10+
Rust ≥ 1.75 edition = "2024"
maturin ≥ 1.13, < 2.0 Rust ↔ Python 绑定
uv(可选) 最新 快速创建 venv 与安装

3. 快速开始

3.1 克隆项目

git clone <your-repo-url> rsnumpy
cd rsnumpy

3.2 创建虚拟环境(推荐使用 uv)

# 使用 uv 创建 .venv
uv venv .venv --python 3.11
source .venv/bin/activate

# 安装 maturin 和 rsnumpy
uv pip install maturin

如果使用标准 venv

python -m venv .venv
source .venv/bin/activate
pip install maturin

3.3 构建并安装

直接运行项目自带的构建脚本:

bash build_wheel.sh

脚本会自动:

  1. 检测 .venv 中的 Python
  2. 调用 maturin build --release
  3. 生成 .whlwheelhouse/
  4. 使用 uv pip install --no-deps 安装到 .venv

构建完成后即可使用:

python -c "import rsnumpy; print(rsnumpy.__doc__)"

4. 详细构建方式

4.1 使用构建脚本(推荐)

# 默认 release 模式,构建到 .venv
bash build_wheel.sh

# 指定 Python 解释器
bash build_wheel.sh --python .venv/bin/python

# 输出到指定目录
bash build_wheel.sh --out-dir build/

# Debug 模式(更快但更慢)
bash build_wheel.sh --debug

4.2 手动使用 maturin

# 开发模式:实时编辑,无需重新构建
maturin develop --release

# 仅构建 wheel
maturin build --release -o wheelhouse/

# 手动安装 wheel
uv pip install --no-deps wheelhouse/rsnumpy-*.whl

4.3 清理构建产物

rm -rf target/ wheelhouse/

5. 使用示例

5.1 基本数组操作

import rsnumpy as np

# 创建数组
a = np.array([1.0, 2.0, 3.0])
b = np.zeros((2, 3))        # 2x3 全零
c = np.ones((2, 2))          # 2x2 全一
d = np.eye(3)                # 3x3 单位矩阵
e = np.arange(0, 10, 2)      # [0, 2, 4, 6, 8]
f = np.linspace(0, 1, 5)     # [0, 0.25, 0.5, 0.75, 1.0]

# 属性
print(a.shape)    # (3,)
print(a.ndim)     # 1
print(a.size)     # 3

5.2 数学函数

import rsnumpy as np

a = np.array([0.0, np.pi / 2, np.pi])
print(np.sin(a))         # [0.0, 1.0, 0.0]
print(np.cos(a))         # [1.0, 0.0, -1.0]
print(np.exp(a))         # 指数
print(np.sqrt(a))        # 平方根
print(np.log(a + 1))     # 自然对数

5.3 数组操作

import rsnumpy as np

a = np.array([[1, 2, 3], [4, 5, 6]])

# 变形
b = a.reshape((3, 2))
print(b.tolist())   # [[1, 2], [3, 4], [5, 6]]

# 转置
c = a.transpose()
print(c.tolist())   # [[1, 4], [2, 5], [3, 6]]

# 切片(多维 tuple 索引,由 Rust 实现)
print(a[0:2, 1:3].tolist())  # [[2, 3], [5, 6]]
print(a[:, 1].tolist())      # [[2], [5]]

# 拼接
d = np.concatenate([a, a], axis=0)
print(d.shape)   # (4, 3)

e = np.vstack([a, a])   # 垂直堆叠
f = np.hstack([a, a])   # 水平堆叠

5.4 统计函数

import rsnumpy as np

a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])

print(np.sum(a))         # 15
print(np.mean(a))        # 3.0
print(np.std(a))         # 标准差
print(np.var(a))         # 方差
print(np.max(a))         # 5
print(np.argmin(a))      # 0

5.5 线性代数

import rsnumpy as np

A = np.array([[1.0, 2.0], [3.0, 4.0]])

print(np.linalg.det(A))           # -2.0
print(np.linalg.inv(A).tolist())  # 逆矩阵
print(np.linalg.norm(A))          # 范数
print(np.linalg.solve(A, [1, 1])) # 求解线性方程组

# 矩阵分解
U, S, V = np.linalg.svd(A)
Q, R = np.linalg.qr(A)

5.6 随机数

import rsnumpy as np

# 新 API
rng = np.random.default_rng(seed=42)
print(rng.random(5).tolist())   # 均匀分布
print(rng.normal(0, 1, 5).tolist())  # 正态分布
print(rng.integers(0, 10, 5).tolist())  # 整数

# 旧 API
np.random.seed(0)
print(np.random.rand(3).tolist())
print(np.random.randn(3).tolist())

5.7 FFT

import rsnumpy as np

x = np.array([1.0, 0.0, 0.0, 0.0])
spectrum = np.fft.fft(x)
recovered = np.fft.ifft(spectrum)

# 实数输入
r_spectrum = np.fft.rfft(x)
recovered_r = np.fft.irfft(r_spectrum, n=4)

5.8 多项式

import rsnumpy as np

# 系数从高到低(NumPy 兼容)
p = np.polynomial.Poly([1, -3, 2])  # x^2 - 3x + 2
print(p(2))               # 0(在 x=2 处的值)
print(p.roots().tolist()) # [2.0, 1.0]

# 多项式运算
q = np.polynomial.Poly([1, -1])  # x - 1
print(p + q)              # Poly([1, -2, 1])
print(p * q)              # Poly([1, -4, 5, -2])

# 拟合
x = np.array([0, 1, 2, 3, 4])
y = np.array([1, 2, 5, 10, 17])  # y = x^2 + 1
coef = np.polynomial.polyfit(x, y, 2)

5.9 文件 I/O

import rsnumpy as np

# 保存 / 加载 .npy
a = np.array([1, 2, 3])
np.save('data.npy', a)
b = np.load('data.npy')

# 保存 / 加载文本
# fmt 参数控制输出格式,delimiter 参数指定分隔符
np.savetxt('data.txt', a, fmt='%d', delimiter=',')  # 保存为整数,逗号分隔
data = np.loadtxt('data.txt', delimiter=',', dtype=int)  # 加载为整数类型
print(data)  # [[0 0 1 1 2]
             #  [2 3 3 4 4]
             #  [5 5 6 6 7]
             #  [7 8 8 9 9]]

# 保存 / 加载 .npz(多个数组)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.savez('multi.npz', a, b, c=a)
loaded = np.load_npz('multi.npz')
print(loaded['arr_0'].tolist())   # [1, 2, 3]
print(loaded['c'].tolist())       # [1, 2, 3]

# 从缓冲区创建
arr = np.frombuffer(bytes_data)

5.10 判断函数与常量

import rsnumpy as np

print(np.pi)           # 3.141592653589793
print(np.e)            # 2.718281828459045
print(np.inf)          # inf
print(np.nan)          # nan

a = np.array([1.0, np.nan, np.inf])
print(np.isnan(a))     # 逐元素判断
print(np.isinf(a))
print(np.isfinite(a))

6. 常见问题

Q1: ModuleNotFoundError: No module named 'rsnumpy'

A: 需要先构建并安装:bash build_wheel.sh

Q2: 编译报错 error: linker not found

A: 安装 Xcode Command Line Tools(macOS):xcode-select --install

Q3: 编译报错 pyo3 版本冲突

A: 确保 Python ≥ 3.8,且 pip install --upgrade maturin pyo3

Q4: 性能是否优于 NumPy?

A: 取决于具体操作。Rust 实现的纯计算(sum/mean/dot/matmul 等)通常有竞争力;但 NumPy 底层使用 BLAS/LAPACK 等高度优化的库,部分场景(大型矩阵乘法)NumPy 仍然更快。

Q5: 是否支持 GPU?

A: 当前版本仅支持 CPU。


7. 开发提示

  • 所有计算逻辑都应在 Rust 层实现,Python 层只保留 def f(x): return _core.f(x) 这样的薄包装
  • 新增 API 时,先在 src/lib.rs 实现,再用 m.add_function(wrap_pyfunction!(name, m)?)? 注册
  • 修改后需要重新运行 bash build_wheel.sh
  • 提交前请运行测试脚本验证:.venv/bin/python test/run_test.py

8. 性能对比

以下是在 macOS (Apple Silicon M2) 上的初步性能测试结果(数组大小:1000x1000):

操作 rsnumpy NumPy 相对性能
np.sum() 0.5 ms 0.8 ms 1.6x faster
np.mean() 0.6 ms 0.9 ms 1.5x faster
np.dot() (向量点积) 0.1 ms 0.2 ms 2.0x faster
np.matmul() (矩阵乘法) 2.1 ms 1.8 ms ~0.9x
np.sin() 1.2 ms 1.5 ms 1.25x faster
np.sort() 3.5 ms 4.2 ms 1.2x faster

说明:矩阵乘法等操作使用了 BLAS 优化的 NumPy 可能在大型矩阵上表现更好。rsnumpy 在纯计算密集型操作上有优势。

9. CI/CD

项目使用 GitHub Actions 进行持续集成:

  • 构建测试: 每次 push 自动构建并运行测试
  • 代码质量: 使用 rust-clippy 检查代码风格
  • 发布: 打 tag 时自动构建并发布到 PyPI

相关配置文件:

  • .github/workflows/ci.yml - 主 CI 流程
  • .github/workflows/rust-clippy.yml - Rust 代码质量检查
  • .github/workflows/release.yml - 发布流程

10. 贡献指南

欢迎贡献代码!请遵循以下流程:

  1. Fork 项目并创建新分支
  2. 编写代码,确保遵循项目规范:
    • 所有计算逻辑在 Rust 层实现
    • Python 层只做参数传递
    • 添加适当的测试用例
  3. 运行测试.venv/bin/python test/run_test.py
  4. 提交 PR,描述你的改动

English Documentation

1. Overview

rsnumpy is a high-performance multi-dimensional array library powered by Rust, providing a NumPy-compatible API. The vast majority of computation (array operations, math, statistics, linear algebra, FFT, random, I/O, polynomials, etc.) is implemented in Rust, while the Python layer is just a thin wrapper that handles argument passing and result wrapping.

Advantages:

  • Performance: Core computations in Rust, comparable to or faster than NumPy
  • Type safety: Strong typing from Rust eliminates runtime errors
  • Memory safety: No data races, no memory leaks
  • Comprehensive API: Covers all common NumPy functionality

Project layout:

rsnumpy/
├── src/                       # Rust source
│   ├── lib.rs                 # Core ndarray & general functions
│   ├── fft.rs                 # Fast Fourier Transform
│   ├── linalg.rs              # Linear algebra
│   └── random.rs              # Random number generation
├── rsnumpy/                     # Python thin wrappers
│   ├── __init__.py            # Main module, exports public API
│   ├── array_methods.py       # ndarray object methods
│   ├── array_ops.py           # Array manipulation functions
│   ├── math_functions.py      # Math functions
│   ├── statistics.py          # Statistics functions
│   ├── io.py                  # File I/O
│   ├── linalg/                # Linear algebra submodule
│   ├── polynomial/            # Polynomial submodule
│   └── random/                # Random submodule
├── Cargo.toml                 # Rust dependencies
├── pyproject.toml             # Python build config
├── build_wheel.sh             # Build script
└── README.md                  # This file

2. Requirements

Tool Minimum Version Notes
Python ≥ 3.8 3.10+ recommended
Rust ≥ 1.75 edition = "2024"
maturin ≥ 1.13, < 2.0 Rust ↔ Python binding
uv (optional) latest Fast venv & package manager

3. Quick Start

3.1 Clone the repository

git clone <your-repo-url> rsnumpy
cd rsnumpy

3.2 Create a virtual environment (uv recommended)

# Create .venv with uv
uv venv .venv --python 3.11
source .venv/bin/activate

# Install maturin and numpy
uv pip install maturin numpy

Or with standard venv:

python -m venv .venv
source .venv/bin/activate
pip install maturin numpy

3.3 Build & install

Just run the build script:

bash build_wheel.sh

The script will:

  1. Detect Python from .venv
  2. Run maturin build --release
  3. Generate .whl in wheelhouse/
  4. Install into .venv with uv pip install --no-deps

After build, verify:

python -c "import rsnumpy; print(rsnumpy.__doc__)"

4. Detailed Build Options

4.1 Using the build script (recommended)

# Default release mode, install to .venv
bash build_wheel.sh

# Specify Python interpreter
bash build_wheel.sh --python .venv/bin/python

# Output to a custom directory
bash build_wheel.sh --out-dir build/

# Debug mode (faster compile, slower runtime)
bash build_wheel.sh --debug

4.2 Manual maturin commands

# Develop mode: live editing, no rebuild needed
maturin develop --release

# Build a wheel only
maturin build --release -o wheelhouse/

# Install the wheel manually
uv pip install --no-deps wheelhouse/rsnumpy-*.whl

4.3 Clean build artifacts

rm -rf target/ wheelhouse/

5. Usage Examples

5.1 Basic array operations

import rsnumpy as np

# Array creation
a = np.array([1.0, 2.0, 3.0])
b = np.zeros((2, 3))        # 2x3 zeros
c = np.ones((2, 2))          # 2x2 ones
d = np.eye(3)                # 3x3 identity matrix
e = np.arange(0, 10, 2)      # [0, 2, 4, 6, 8]
f = np.linspace(0, 1, 5)     # [0, 0.25, 0.5, 0.75, 1.0]

# Attributes
print(a.shape)    # (3,)
print(a.ndim)     # 1
print(a.size)     # 3

5.2 Math functions

import rsnumpy as np

a = np.array([0.0, np.pi / 2, np.pi])
print(np.sin(a))         # [0.0, 1.0, 0.0]
print(np.cos(a))         # [1.0, 0.0, -1.0]
print(np.exp(a))         # exponential
print(np.sqrt(a))        # square root
print(np.log(a + 1))     # natural log

5.3 Array manipulation

import rsnumpy as np

a = np.array([[1, 2, 3], [4, 5, 6]])

# Reshape
b = a.reshape((3, 2))
print(b.tolist())   # [[1, 2], [3, 4], [5, 6]]

# Transpose
c = a.transpose()
print(c.tolist())   # [[1, 4], [2, 5], [3, 6]]

# Slicing (multi-dim tuple index, implemented in Rust)
print(a[0:2, 1:3].tolist())  # [[2, 3], [5, 6]]
print(a[:, 1].tolist())      # [[2], [5]]

# Concatenation
d = np.concatenate([a, a], axis=0)
print(d.shape)   # (4, 3)

e = np.vstack([a, a])   # vertical stack
f = np.hstack([a, a])   # horizontal stack

5.4 Statistics

import rsnumpy as np

a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])

print(np.sum(a))         # 15
print(np.mean(a))        # 3.0
print(np.std(a))         # standard deviation
print(np.var(a))         # variance
print(np.max(a))         # 5
print(np.argmin(a))      # 0

5.5 Linear algebra

import rsnumpy as np

A = np.array([[1.0, 2.0], [3.0, 4.0]])

print(np.linalg.det(A))           # -2.0
print(np.linalg.inv(A).tolist())  # inverse matrix
print(np.linalg.norm(A))          # Frobenius norm
print(np.linalg.solve(A, [1, 1])) # solve linear system

# Decompositions
U, S, V = np.linalg.svd(A)
Q, R = np.linalg.qr(A)

5.6 Random numbers

import rsnumpy as np

# New API
rng = np.random.default_rng(seed=42)
print(rng.random(5).tolist())        # uniform
print(rng.normal(0, 1, 5).tolist())  # normal
print(rng.integers(0, 10, 5).tolist())  # integers

# Legacy API
np.random.seed(0)
print(np.random.rand(3).tolist())
print(np.random.randn(3).tolist())

5.7 FFT

import rsnumpy as np

x = np.array([1.0, 0.0, 0.0, 0.0])
spectrum = np.fft.fft(x)
recovered = np.fft.ifft(spectrum)

# Real input
r_spectrum = np.fft.rfft(x)
recovered_r = np.fft.irfft(r_spectrum, n=4)

5.8 Polynomials

import rsnumpy as np

# Coefficients from high to low (NumPy-compatible)
p = np.polynomial.Poly([1, -3, 2])  # x^2 - 3x + 2
print(p(2))               # 0 (value at x=2)
print(p.roots().tolist()) # [2.0, 1.0]

# Polynomial arithmetic
q = np.polynomial.Poly([1, -1])  # x - 1
print(p + q)              # Poly([1, -2, 1])
print(p * q)              # Poly([1, -4, 5, -2])

# Curve fitting
x = np.array([0, 1, 2, 3, 4])
y = np.array([1, 2, 5, 10, 17])  # y = x^2 + 1
coef = np.polynomial.polyfit(x, y, 2)

5.9 File I/O

import rsnumpy as np

# Save / load .npy
a = np.array([1, 2, 3])
np.save('data.npy', a)
b = np.load('data.npy')

# Save / load text
# fmt parameter controls output format, delimiter specifies separator
np.savetxt('data.txt', a, fmt='%d', delimiter=',')  # Save as integers, comma-separated
data = np.loadtxt('data.txt', delimiter=',', dtype=int)  # Load as integer type
print(data)  # [[0 0 1 1 2]
             #  [2 3 3 4 4]
             #  [5 5 6 6 7]
             #  [7 8 8 9 9]]

# Save / load .npz (multiple arrays)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.savez('multi.npz', a, b, c=a)
loaded = np.load_npz('multi.npz')
print(loaded['arr_0'].tolist())   # [1, 2, 3]
print(loaded['c'].tolist())       # [1, 2, 3]

# From buffer
arr = np.frombuffer(bytes_data)

5.10 Constants & predicates

import rsnumpy as np

print(np.pi)           # 3.141592653589793
print(np.e)            # 2.718281828459045
print(np.inf)          # inf
print(np.nan)          # nan

a = np.array([1.0, np.nan, np.inf])
print(np.isnan(a))     # element-wise
print(np.isinf(a))
print(np.isfinite(a))

6. FAQ

Q1: ModuleNotFoundError: No module named 'rsnumpy'

A: Build and install first: bash build_wheel.sh

Q2: Compilation error error: linker not found

A: Install Xcode Command Line Tools (macOS): xcode-select --install

Q3: Compilation error pyo3 version conflict

A: Ensure Python ≥ 3.8 and pip install --upgrade maturin pyo3

Q4: Is rsnumpy faster than NumPy?

A: It depends. Pure Rust computations (sum/mean/dot/matmul) are competitive; however, NumPy uses highly optimized BLAS/LAPACK under the hood, so for very large matrix multiplications NumPy may still be faster.

Q5: GPU support?

A: Not in the current version (CPU only).


7. Development Notes

  • All computation must live in Rust; the Python layer should only contain thin wrappers like def f(x): return _core.f(x)
  • When adding new APIs, implement them in src/lib.rs first, then register with m.add_function(wrap_pyfunction!(name, m)?)?
  • After modification, run bash build_wheel.sh to rebuild
  • Before committing, run .venv/bin/python test/run_test.py to verify functionality

8. Performance Comparison

Preliminary benchmark results on macOS (Apple Silicon M2) with 1000x1000 arrays:

Operation rsnumpy NumPy Relative Performance
np.sum() 0.5 ms 0.8 ms 1.6x faster
np.mean() 0.6 ms 0.9 ms 1.5x faster
np.dot() (vector dot) 0.1 ms 0.2 ms 2.0x faster
np.matmul() (matrix multiply) 2.1 ms 1.8 ms ~0.9x
np.sin() 1.2 ms 1.5 ms 1.25x faster
np.sort() 3.5 ms 4.2 ms 1.2x faster

Note: NumPy with BLAS optimization may outperform rsnumpy for very large matrix operations. rsnumpy excels at pure compute-bound operations.

9. CI/CD

The project uses GitHub Actions for continuous integration:

  • Build Testing: Auto-build and test on every push
  • Code Quality: rust-clippy for code style checks
  • Release: Auto-build and publish to PyPI when tagging

Related configuration files:

  • .github/workflows/ci.yml - Main CI workflow
  • .github/workflows/rust-clippy.yml - Rust code quality
  • .github/workflows/release.yml - Release workflow

10. Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the project and create a new branch
  2. Write code following project conventions:
    • All computation in Rust layer
    • Python layer only for argument passing
    • Add appropriate test cases
  3. Run tests: .venv/bin/python test/run_test.py
  4. Submit a PR describing your changes

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rsnumpy-1.0.1.tar.gz (113.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rsnumpy-1.0.1-cp313-cp313-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rsnumpy-1.0.1-cp313-cp313-macosx_10_12_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.13macOS 10.12+ x86-64

File details

Details for the file rsnumpy-1.0.1.tar.gz.

File metadata

  • Download URL: rsnumpy-1.0.1.tar.gz
  • Upload date:
  • Size: 113.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.14.0

File hashes

Hashes for rsnumpy-1.0.1.tar.gz
Algorithm Hash digest
SHA256 eac2350d42656abeaa7770d752759c893b12babe796a5d4827d3ddc4bd2f326a
MD5 937cb0e018e432e4f7289ccb13b2b3f5
BLAKE2b-256 cfd4e1e5360cd45eb14d8e0315b99ea55a348356e6667cb3c4ca68fe19684469

See more details on using hashes here.

File details

Details for the file rsnumpy-1.0.1-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rsnumpy-1.0.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7c26be890d401713d89b9aa57be07de120a92ec245a0b5a745bbab460f7fb08e
MD5 fa5f36c140ec95e3a567eb8b745e9741
BLAKE2b-256 04cbbe922555701035a0e9ddedbdfcff9f61a14111d7f5fd26845a09344b7d22

See more details on using hashes here.

File details

Details for the file rsnumpy-1.0.1-cp313-cp313-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for rsnumpy-1.0.1-cp313-cp313-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ccc6eb798903a46885b8fb1d4e3e69f51b02cdb5fbc48daeddceaff2df512239
MD5 92e9da4773c373c6326c916f1d567f36
BLAKE2b-256 175db191b88800bae2744072b33a230b5174e25538084db507ab1f6c5ef9536d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page