Skip to content

Commit f7457cc

Browse files
committed
Python binding SIMD vs Scalar dynamic dispatch
1 parent 8fc84ab commit f7457cc

File tree

7 files changed

+147
-64
lines changed

7 files changed

+147
-64
lines changed

bindings/CMakeLists.txt

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,37 @@ target_link_libraries(quickedcpp PRIVATE quicked)
77
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
88
add_subdirectory(python/pybind11)
99

10-
pybind11_add_module(pyquicked python/quicked.cpp cpp/quicked.cpp)
11-
set_target_properties(pyquicked PROPERTIES OUTPUT_NAME quicked ARCHIVE_OUTPUT_NAME pyquicked)
12-
target_include_directories(pyquicked PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cpp)
13-
target_link_libraries(pyquicked PRIVATE quicked)
10+
pybind11_add_module(pyquicked_scalar python/quicked.cpp cpp/quicked.cpp)
11+
target_compile_definitions(pyquicked_scalar PRIVATE PY_MODULE_NAME=_quicked_scalar)
12+
set_target_properties(pyquicked_scalar PROPERTIES OUTPUT_NAME _quicked_scalar ARCHIVE_OUTPUT_NAME pyquicked_scalar)
13+
target_include_directories(pyquicked_scalar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cpp)
14+
target_link_libraries(pyquicked_scalar PRIVATE quicked_scalar)
1415
if (NOT MSVC)
15-
target_compile_options(pyquicked PRIVATE -fvisibility=hidden)
16+
target_compile_options(pyquicked_scalar PRIVATE -fvisibility=hidden)
17+
target_compile_options(pyquicked_scalar PRIVATE -mno-sse4.1 -mno-avx2) # Disable SSE4.1 and AVX2 for the scalar Python binding
18+
else()
19+
target_compile_options(pyquicked_scalar PRIVATE /arch:IA32) # Disable SSE4.1 and AVX2 for the scalar Python binding
20+
target_compile_definitions(pyquicked_scalar PRIVATE _M_IX86)
1621
endif()
1722

18-
install(TARGETS pyquicked DESTINATION . COMPONENT pyquicked EXCLUDE_FROM_ALL)
19-
install(FILES python/quicked.pyi DESTINATION . COMPONENT pyquicked EXCLUDE_FROM_ALL)
23+
24+
pybind11_add_module(pyquicked_simd python/quicked.cpp cpp/quicked.cpp)
25+
target_compile_definitions(pyquicked_simd PRIVATE PY_MODULE_NAME=_quicked_simd)
26+
set_target_properties(pyquicked_simd PROPERTIES OUTPUT_NAME _quicked_simd ARCHIVE_OUTPUT_NAME pyquicked_simd)
27+
target_include_directories(pyquicked_simd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cpp)
28+
target_link_libraries(pyquicked_simd PRIVATE quicked_simd)
29+
if (NOT MSVC)
30+
target_compile_options(pyquicked_simd PRIVATE -fvisibility=hidden)
31+
target_compile_options(pyquicked_simd PRIVATE -mno-sse4.1 -mno-avx2) # Enable SSE4.1 and AVX2 for the SIMD Python binding
32+
else()
33+
target_compile_options(pyquicked_simd PRIVATE /arch:AVX) # Enable SSE4.1 and AVX2 for the SIMD Python binding
34+
endif()
35+
36+
add_custom_target(pyquicked ALL
37+
DEPENDS pyquicked_scalar pyquicked_simd
38+
)
39+
40+
install(TARGETS pyquicked_scalar DESTINATION quicked/backend COMPONENT pyquicked EXCLUDE_FROM_ALL)
41+
install(TARGETS pyquicked_simd DESTINATION quicked/backend COMPONENT pyquicked EXCLUDE_FROM_ALL)
42+
install(FILES python/__init__.py DESTINATION quicked COMPONENT pyquicked EXCLUDE_FROM_ALL)
43+
install(FILES python/quicked.pyi DESTINATION quicked COMPONENT pyquicked EXCLUDE_FROM_ALL)

bindings/python/__init__.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
QuickEd - A high-performance exact sequence alignment based on the bound-and-align paradigm
3+
===========================================================================================
4+
5+
QuickEd is a high-performance exact sequence alignment based on the bound-and-align paradigm.
6+
Currently, QuickEd focuses on DNA sequence alignment, using the edit distance (Levenshtein distance) metric.
7+
8+
>>> pattern = "ACGT"
9+
>>> text = "ACTT"
10+
>>> aligner = quicked.QuickedAligner()
11+
>>> aligner.align(pattern, text)
12+
>>> score = aligner.getScore()
13+
>>> cigar = aligner.getCigar()
14+
15+
QuickEd is actually a C library, and this package is it’s wrapper for Python.
16+
Check out QuickEd's GitHub for more code examples and more details on how QuickEd works.
17+
"""
18+
def __cpu_supports_simd():
19+
try:
20+
import cpuinfo
21+
22+
info = cpuinfo.get_cpu_info()
23+
arch = info.get("arch", "").lower()
24+
if "x86" not in arch:
25+
return False
26+
flags = info.get("flags", [])
27+
return "avx2" in flags and "sse4_1" in flags
28+
except Exception:
29+
return False
30+
31+
32+
def __load_compiled_backend(name: str):
33+
import os
34+
import importlib.util
35+
import importlib.machinery
36+
37+
base_dir = os.path.dirname(__file__)
38+
backend_dir = os.path.join(base_dir, "backend")
39+
for ext in importlib.machinery.EXTENSION_SUFFIXES:
40+
candidate = os.path.join(backend_dir, name + ext)
41+
if os.path.isfile(candidate):
42+
loader = importlib.machinery.ExtensionFileLoader(name, candidate)
43+
spec = importlib.util.spec_from_file_location(
44+
name, candidate, loader=loader
45+
)
46+
mod = importlib.util.module_from_spec(spec)
47+
spec.loader.exec_module(mod)
48+
return mod
49+
raise ImportError(f"Cannot find compiled backend: {name}")
50+
51+
52+
backend = "_quicked_simd" if __cpu_supports_simd() else "_quicked_scalar"
53+
"""
54+
The name of the backend module to load, determined based on CPU SIMD support.
55+
If the CPU supports AVX2 and SSE4.1, the SIMD-optimized backend ("_quicked_simd") is used.
56+
Otherwise, the scalar backend ("_quicked_scalar") is selected.
57+
"""
58+
59+
__backend_module = __load_compiled_backend(backend)
60+
61+
# Import public symbols
62+
__public_symbols = getattr(
63+
__backend_module,
64+
"__all__",
65+
[k for k in dir(__backend_module) if not k.startswith("_")],
66+
)
67+
globals().update({k: getattr(__backend_module, k) for k in __public_symbols})
68+
__all__ = __public_symbols + ["backend"]
69+

bindings/python/quicked.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@
2828
namespace py = pybind11;
2929
using namespace py::literals;
3030

31+
#ifndef PY_MODULE_NAME
32+
#define PY_MODULE_NAME _quicked
33+
#endif
34+
3135
namespace quicked {
32-
PYBIND11_MODULE(quicked, m) {
36+
PYBIND11_MODULE(PY_MODULE_NAME, m) {
3337
m.doc() = R"pbdoc(
3438
QuickEd - A high-performance exact sequence alignment based on the bound-and-align paradigm
3539
===========================================================================================

bindings/python/quicked.pyi

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,27 @@
1-
"""
2-
3-
QuickEd - A high-performance exact sequence alignment based on the bound-and-align paradigm
4-
===========================================================================================
5-
6-
QuickEd is a high-performance exact sequence alignment based on the bound-and-align paradigm.
7-
Currently, QuickEd focuses on DNA sequence alignment, using the edit distance (Levenshtein distance) metric.
8-
9-
>>> pattern = "ACGT"
10-
>>> text = "ACTT"
11-
>>> aligner = quicked.QuickedAligner()
12-
>>> aligner.align(pattern, text)
13-
>>> score = aligner.getScore()
14-
>>> cigar = aligner.getCigar()
15-
16-
QuickEd is actually a C library, and this package is it’s wrapper for Python.
17-
Check out QuickEd's GitHub for more code examples and more details on how QuickEd works.
18-
"""
191
from __future__ import annotations
202
import typing
213
__all__ = ['QuickedAlgo', 'QuickedAligner', 'QuickedException']
224
class QuickedAlgo:
235
"""
24-
256
QuickedAlgo
267
===========
278
28-
Enumeration of alignment algorithms
9+
Enumeration of alignment algorithms.
2910
30-
Members:
31-
QUICKED : QuickEd algorithm
32-
WINDOWED : WindowEd algorithm
33-
BANDED : BandEd algorithm
34-
HIRSCHBERG : Hirschberg algorithm
11+
Attributes:
12+
QUICKED (QuickedAlgo): QuickEd algorithm.
13+
WINDOWED (QuickedAlgo): WindowEd algorithm.
14+
BANDED (QuickedAlgo): BandEd algorithm.
15+
HIRSCHBERG (QuickedAlgo): Hirschberg algorithm.
3516
"""
3617
BANDED: typing.ClassVar[QuickedAlgo] # value = <QuickedAlgo.BANDED: 2>
3718
HIRSCHBERG: typing.ClassVar[QuickedAlgo] # value = <QuickedAlgo.HIRSCHBERG: 3>
3819
QUICKED: typing.ClassVar[QuickedAlgo] # value = <QuickedAlgo.QUICKED: 0>
3920
WINDOWED: typing.ClassVar[QuickedAlgo] # value = <QuickedAlgo.WINDOWED: 1>
4021
__members__: typing.ClassVar[dict[str, QuickedAlgo]] # value = {'QUICKED': <QuickedAlgo.QUICKED: 0>, 'WINDOWED': <QuickedAlgo.WINDOWED: 1>, 'BANDED': <QuickedAlgo.BANDED: 2>, 'HIRSCHBERG': <QuickedAlgo.HIRSCHBERG: 3>}
41-
def __eq__(self, other: typing.Any) -> bool:
42-
...
43-
def __getstate__(self) -> int:
44-
...
45-
def __hash__(self) -> int:
46-
...
47-
def __index__(self) -> int:
48-
...
49-
def __init__(self, value: int) -> None:
50-
...
51-
def __int__(self) -> int:
52-
...
53-
def __ne__(self, other: typing.Any) -> bool:
54-
...
55-
def __repr__(self) -> str:
56-
...
57-
def __setstate__(self, state: int) -> None:
58-
...
59-
def __str__(self) -> str:
60-
...
61-
@property
62-
def name(self) -> str:
63-
...
64-
@property
65-
def value(self) -> int:
66-
...
22+
6723
class QuickedAligner:
6824
"""
69-
7025
QuickedAligner
7126
==============
7227
@@ -120,5 +75,6 @@ class QuickedAligner:
12075
"""
12176
Sets the window size for WindowEd alignment
12277
"""
78+
12379
class QuickedException(Exception):
12480
pass

examples/bindings/align_benchmark.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import timeit
55

6-
from quicked import QuickedAlgo, QuickedAligner, QuickedException
6+
from quicked import QuickedAligner, QuickedException, backend
77

88
if len(sys.argv) < 2:
99
print("Usage: python3 test.py <input_file>")
@@ -41,6 +41,7 @@ def align_sequences():
4141
print(f"Error in pair {i // 2 + 1}: {e}")
4242

4343
try:
44+
print("Using backend:", backend)
4445
execution_time = timeit.timeit(align_sequences, number=1)
4546
print(f"Execution time: {execution_time:.2f} seconds")
4647
except QuickedException as e:

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ requires = ["scikit-build-core==0.11.1", "packaging>=24.2"]
33
build-backend = "scikit_build_core.build"
44

55
[tool.scikit-build]
6-
build.verbose = true
6+
cmake.version = "==3.22.1" # Pinning CMake version to avoid conflicts with pinned dependencies
77
logging.level = "INFO"
88
build.targets = ["pyquicked"]
99
install.components = ["pyquicked"]
1010

1111
[tool.cibuildwheel]
1212
environment = { CMAKE_ARGS = "-DQUICKED_NONATIVE=ON" }
1313
archs = ["auto64"]
14-
skip = ["pp*", "musllinux_*"] # Skip PyPy and musllinux builds to cut down on CI time
14+
skip = ["pp*", "*musllinux*"] # Skip PyPy and musllinux builds to cut down on CI time
1515

1616
[project]
1717
name = "QuickEd"

quicked/CMakeLists.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,33 @@ install(FILES
3434
${PROJECT_SOURCE_DIR}/quicked_utils/include/vector.h
3535
DESTINATION include/quicked_utils/include
3636
COMPONENT quicked
37-
)
37+
)
38+
39+
# Shim targets for building the Python bindings
40+
add_library(quicked_scalar OBJECT ${QUICKED_SRCS} ${UTILS_SRCS})
41+
target_include_directories(quicked_scalar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) # Library includes
42+
target_include_directories(quicked_scalar PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # Library root
43+
if(NOT (WIN_CLANG OR MSVC))
44+
target_link_libraries(quicked_scalar PUBLIC m) # Link math library
45+
endif()
46+
if (NOT MSVC)
47+
target_compile_options(quicked_scalar PRIVATE -mno-sse4.1 -mno-avx2) # Disable SSE4.1 and AVX2 for the scalar Python binding
48+
else()
49+
target_compile_options(quicked_scalar PRIVATE /arch:IA32) # Disable SSE4.1 and AVX2 for the scalar Python binding
50+
target_compile_definitions(quicked_scalar PRIVATE _M_IX86)
51+
endif()
52+
set_target_properties(quicked_scalar PROPERTIES EXCLUDE_FROM_ALL TRUE)
53+
54+
55+
add_library(quicked_simd OBJECT ${QUICKED_SRCS} ${UTILS_SRCS})
56+
target_include_directories(quicked_simd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) # Library includes
57+
target_include_directories(quicked_simd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # Library root
58+
if(NOT (WIN_CLANG OR MSVC))
59+
target_link_libraries(quicked_simd PUBLIC m) # Link math library
60+
endif()
61+
if (NOT MSVC)
62+
target_compile_options(quicked_simd PRIVATE -msse4.1 -mavx2) # Enable SSE4.1 and AVX2 for the SIMD Python binding
63+
else()
64+
target_compile_options(quicked_simd PRIVATE /arch:AVX) # Enable SSE4.1 and AVX2 for the SIMD Python binding
65+
endif()
66+
set_target_properties(quicked_simd PROPERTIES EXCLUDE_FROM_ALL TRUE)

0 commit comments

Comments
 (0)