Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing and timing compared to NumPy #288

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
53 changes: 53 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: check

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:

standard:

strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]

defaults:
run:
shell: bash -l {0}

name: ${{ matrix.runs-on }} • x64 ${{ matrix.args }}
runs-on: ${{ matrix.runs-on }}

steps:

- name: Basic GitHub action setup
uses: actions/checkout@v3

- name: Set conda environment
uses: mamba-org/provision-with-micromamba@main
with:
environment-file: environment-dev.yml
environment-name: myenv
cache-env: true
extra-specs: |
prettytable
setuptools_scm
scikit-build
xsimd

- name: Set dummy version
run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=0.0" >> $GITHUB_ENV

- name: Build and install Python module
working-directory: python-module
run: |
SKBUILD_CONFIGURE_OPTIONS="-DUSE_XSIMD=1" python -m pip install . -v

- name: Run Python tests
working-directory: python-module
run: python -m unittest discover tests
53 changes: 53 additions & 0 deletions .github/workflows/profile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: profile

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:

standard:

strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]

defaults:
run:
shell: bash -l {0}

name: ${{ matrix.runs-on }} • x64 ${{ matrix.args }}
runs-on: ${{ matrix.runs-on }}

steps:

- name: Basic GitHub action setup
uses: actions/checkout@v3

- name: Set conda environment
uses: mamba-org/provision-with-micromamba@main
with:
environment-file: environment-dev.yml
environment-name: myenv
cache-env: true
extra-specs: |
prettytable
setuptools_scm
scikit-build
xsimd

- name: Set dummy version
run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=0.0" >> $GITHUB_ENV

- name: Build and install Python module
working-directory: python-module
run: |
SKBUILD_CONFIGURE_OPTIONS="-DUSE_XSIMD=1" python -m pip install . -v

- name: Run Python profiling
working-directory: python-module
run: python -m unittest discover profiling
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
_skbuild

# Prerequisites
*.d

Expand Down
4 changes: 2 additions & 2 deletions environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ dependencies:
# Build dependencies
- cmake
# Host dependencies
- xtensor=0.24.0
- xtensor >=0.24.0
- numpy
- pybind11=2.4.3
- pybind11 >=2.4.3
# Test dependencies
- pytest

63 changes: 63 additions & 0 deletions python-module/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.18..3.21)

project(xt)

# The C++ functions are build to a library with name "_${PROJECT_NAME}"
# The Python library simply loads all functions
set(PYPROJECT_NAME "_${PROJECT_NAME}")

option(USE_WARNINGS "${PROJECT_NAME}: Build with runtime warnings" ON)
option(USE_XSIMD "${PROJECT_NAME}: Build with hardware optimization" OFF)

if (DEFINED ENV{SETUPTOOLS_SCM_PRETEND_VERSION})
set(PROJECT_VERSION $ENV{SETUPTOOLS_SCM_PRETEND_VERSION})
message(STATUS "Building ${PROJECT_NAME} ${PROJECT_VERSION} (read from SETUPTOOLS_SCM_PRETEND_VERSION)")
else()
execute_process(
COMMAND python -c "from setuptools_scm import get_version; print(get_version(root='..'))"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE PROJECT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)

message(STATUS "Building ${PROJECT_NAME} ${PROJECT_VERSION}")
endif()

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

find_package(xtensor REQUIRED)
find_package(pybind11 REQUIRED CONFIG)

if (SKBUILD)
find_package(NumPy REQUIRED)
else()
find_package(Python REQUIRED COMPONENTS Interpreter Development NumPy)
endif()

pybind11_add_module(${PYPROJECT_NAME} module/main.cpp)

target_compile_definitions(${PYPROJECT_NAME} PUBLIC VERSION_INFO=${PROJECT_VERSION})
target_link_libraries(${PYPROJECT_NAME} PUBLIC xtensor)
target_include_directories(${PYPROJECT_NAME} PUBLIC "../include")

if (SKBUILD)
target_include_directories(${PYPROJECT_NAME} PUBLIC ${NumPy_INCLUDE_DIRS})
else()
target_link_libraries(${PYPROJECT_NAME} PUBLIC pybind11::module Python::NumPy)
endif()

if (USE_SIMD)
find_package(xsimd REQUIRED)
target_link_libraries(${PYPROJECT_NAME} PUBLIC xtensor::optimize xtensor::use_xsimd)
message(STATUS "Compiling ${PROJECT_NAME}-Python with hardware optimization")
endif()

if (SKBUILD)
if(APPLE)
set_target_properties(${PYPROJECT_NAME} PROPERTIES INSTALL_RPATH "@loader_path/${CMAKE_INSTALL_LIBDIR}")
else()
set_target_properties(${PYPROJECT_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/${CMAKE_INSTALL_LIBDIR}")
endif()
install(TARGETS ${PYPROJECT_NAME} DESTINATION .)
endif()
60 changes: 60 additions & 0 deletions python-module/module/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @file
* @copyright Copyright 2020. Tom de Geus. All rights reserved.
* @license This project is released under the GNU Public License (MIT).
*/

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#define FORCE_IMPORT_ARRAY
#include <xtensor-python/pyarray.hpp>
#include <xtensor-python/pytensor.hpp>
#include <xtensor.hpp>

namespace py = pybind11;

/**
* Overrides the `__name__` of a module.
* Classes defined by pybind11 use the `__name__` of the module as of the time they are defined,
* which affects the `__repr__` of the class type objects.
*/
class ScopedModuleNameOverride {
public:
explicit ScopedModuleNameOverride(py::module m, std::string name) : module_(std::move(m))
{
original_name_ = module_.attr("__name__");
module_.attr("__name__") = name;
}
~ScopedModuleNameOverride()
{
module_.attr("__name__") = original_name_;
}

private:
py::module module_;
py::object original_name_;
};

PYBIND11_MODULE(_xt, m)
{
// Ensure members to display as `xt.X` (not `xt._xt.X`)
ScopedModuleNameOverride name_override(m, "xt");

xt::import_numpy();

m.doc() = "Python bindings of xtensor";

m.def("mean", [](const xt::pyarray<double>& a) -> xt::pyarray<double> { return xt::mean(a); });
m.def("average", [](const xt::pyarray<double>& a, const xt::pyarray<double>& w) -> xt::pyarray<double> { return xt::average(a, w); });
m.def("average", [](const xt::pyarray<double>& a, const xt::pyarray<double>& w, const std::vector<ptrdiff_t>& axes) -> xt::pyarray<double> { return xt::average(a, w, axes); });

m.def("flip", [](const xt::pyarray<double>& a, ptrdiff_t axis) -> xt::pyarray<double> { return xt::flip(a, axis); });

m.def("cos", [](const xt::pyarray<double>& a) -> xt::pyarray<double> { return xt::cos(a); });

m.def("isin", [](const xt::pyarray<int>& a, const xt::pyarray<int>& b) -> xt::pyarray<bool> { return xt::isin(a, b); });
m.def("in1d", [](const xt::pyarray<int>& a, const xt::pyarray<int>& b) -> xt::pyarray<bool> { return xt::in1d(a, b); });


}
1 change: 1 addition & 0 deletions python-module/module/xt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._xt import * # noqa: F401, F403
79 changes: 79 additions & 0 deletions python-module/profiling/test_a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import unittest
import timeit
import warnings
from prettytable import PrettyTable

import xt
import numpy as np


class test_a(unittest.TestCase):
"""
??
"""

@classmethod
def setUpClass(self):

self.a = np.random.random([103, 102, 101])
self.axis = int(np.random.randint(0, high=3))
self.data = []

@classmethod
def tearDownClass(self):

self.data = sorted(self.data, key=lambda x: x[1])

table = PrettyTable(["function", "xtensor / numpy"])

for row in self.data:
table.add_row(row)

print("")
print(table)
print("")

# code = table.get_html_string()
# with open('profile.html', 'w') as file:
# file.write(code)

def test_mean(self):

n = timeit.timeit(lambda: np.mean(self.a), number=10)
x = timeit.timeit(lambda: xt.mean(self.a), number=10)
self.data.append(("mean", x / n))

def test_flip(self):

n = timeit.timeit(lambda: np.flip(self.a, self.axis), number=10)
x = timeit.timeit(lambda: xt.flip(self.a, self.axis), number=10)
self.data.append(("flip", x / n))

def test_cos(self):

n = timeit.timeit(lambda: np.cos(self.a), number=10)
x = timeit.timeit(lambda: xt.cos(self.a), number=10)
self.data.append(("cos", x / n))

def test_isin(self):

a = (np.random.random([103, 102]) * 1000).astype(np.intc)
b = (np.random.random([103, 102]) * 1000).astype(np.intc)

n = timeit.timeit(lambda: np.isin(a, b), number=10)
x = timeit.timeit(lambda: xt.isin(a, b), number=10)
self.data.append(("isin", x / n))

def test_in1d(self):

a = (np.random.random([1003]) * 1000).astype(np.intc)
b = (np.random.random([1003]) * 1000).astype(np.intc)

n = timeit.timeit(lambda: np.in1d(a, b), number=10)
x = timeit.timeit(lambda: xt.in1d(a, b), number=10)
self.data.append(("in1d", x / n))


if __name__ == "__main__":

unittest.main()
24 changes: 24 additions & 0 deletions python-module/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pathlib import Path

from setuptools_scm import get_version
from skbuild import setup

project_name = "xt"

this_directory = Path(__file__).parent
long_description = (this_directory / ".." / "README.md").read_text()
license = (this_directory / ".." / "LICENSE").read_text()

setup(
name=project_name,
description="xt - Python bindings of xtensor",
long_description=long_description,
version=get_version(root="..", relative_to=__file__),
license=license,
author="Tom de Geus",
url=f"ttps://github.com/xtensor-stack/{project_name}",
packages=[f"{project_name}"],
package_dir={"": "module"},
cmake_install_dir=f"module/{project_name}",
cmake_minimum_required_version="3.13",
)
Empty file added python-module/tests/__init__.py
Empty file.
Loading