Python is an interpreted programming language that is used in multiple fields, ranging from education, scientific purposes to DevOps and cloud infrastructure. According to http://pypl.github.io/PYPL.html, Python is ranked first in Google’s search popularity.
This post is meant to snapshot the current state of the art of the Python ecosystem on Windows ARM64, focusing on one main area: package support.
Package support is critical for Python developers in order to port their work to this new architecture, especially when relying on third party packages from Pypi or other sources as it usually happens. No one desires to spend time rewriting their application just to support another architecture or use non-upstreamed package sources, which are hard to maintain and non-trivial to test.
For the sake of this post, we are going to use Python 3.9, being the latest stable release. We are going to look at three main scenarios: multi-purpose, scientific and DevOps. We used GitHub actions to create reproducible builds leveraging a parameterized workflow: https://github.com/ader1990/cpython-builder. All packages were either install using pip with the following flags “–force-reinstall –no-binary :all:” or using “python setup.py install“.
Generally speaking, packages that are 100% Python and do not include native code, have no setup issues on ARM64 either. Even at runtime, unless they rely on some specific CPU architectural feature (e.g. checking for os.uname()) there are no reasons for them not to work. The creation of binary launcher is a different story though, as we will see in the next section. Besides that, the real issues typically arise when the package includes native code which needs to be compiled with an ARM64 C/C++ compiler on Windows as part of the setup process unless wheel files are made available on Pypi. We will get into this further down.
Python Multi-Purpose Packages
Here are the most used 25 Python packages in the past 365 days (as of December 2020) according to https://hugovk.github.io/top-pypi-packages/
Out of the 25 packages, 23 were successfully built using the ARM64 Python versions on Windows 10 ARM64. numpy and cffi required patching for ARM64. setuptools and pip, although they could be successfully built, needed special attention in order to work correctly on ARM64:
- setuptools package required patching for the Windows ARM64 launchers so that the executables resulted from the build were created as ARM64 binaries: https://github.com/ader1990/setuptools/commits/am_64. Without the ARM64 launchers, the binaries are created using the x64 launchers and cannot be run on Windows ARM64 without x64 emulation. Even if the x64 emulation is present in the most recent Windows 10 x64 version, the emulation introduces heavy performance penalties, which we will address in an upcoming post. The launchers were created using this simple launcher tree: https://github.com/ader1990/simple_launcher/tree/win_arm64.
- pip package needed similar patching to setuptools, otherwise installation from wheels will create executables for x64: https://github.com/ader1990/pip/tree/win_arm64
There is work in progress to add these changes upstream so that the community can benefit from a proper frustration-free experience when using Windows on ARM64.
Python Scientific Packages
Here are some of the most popular packages in the scientific world: numpy, pandas, scikit and tensorflow. These packages are commonly used as building blocks in the ecosystem and are critical for using Python as the high level language in the AI/ML field.
Build / installation notes:
- Visual Studio 2017 or 2019 (the free Community Edition is enough for open source use cases) and Windows 10 SDK are required for building these packages.
- Numpy needs this patch to be successfully built: https://github.com/numpy/numpy/pull/17804.
- Cython 0.29.x is required to successfully build pandas.
- Pandas has numpy and cython as a requirement, thus cython needs to be installed first and numpy needs to be built before pandas from the patched source.
More information on how to properly setup the build environment can be found here: https://github.com/cloudbase/cloudbase-init-arm-scripts.
Scikit could not be built, because it currently depends on external libraries FreeType, BLAS/LAPACK, which are not yet supported for ARM64, as the build system for Windows relies on Mingw32 (which does not have support yet for ARM64). More investigation is required on how to build those libraries for ARM64.
Tensorflow cannot be built, as it relies on Bazel build system which does not support ARM64 builds yet, we are digging more into this at the moment.
Python DevOps Packages
The DevOps world relies on Python to automate all kinds of processes: public cloud (AWS, Azure, etc), OpenStack services, instance provisioning (cloud-init, cloudbase-init). For this area, I have chosen awscli, cffi, greenlet, pywin32 and cloudbase-init.
The awscli package is the Python client for the Amazon Web Services, widely used by DevOps that want to automate workloads on AWS. The package could be easily installed without any code changes. It was not a surprise to see that the awscli package has more than half of the common multi-purpose packages above as requirements. Another observation is that the installation is slow, takes around 10 minutes to complete. We will cover more on the package installation / functionality performance in an upcoming post.
The cffi package is a recurring requirement for many packages that were initially created for *nix operating systems and were ported to Windows. As from its description, cffi is a “A Foreign Function Interface package for calling C libraries from Python”. To build this package on Windows ARM64, a NOOP patch was required: https://github.com/ader1990/cffi/tree/windows_arm64. There is work in progress to properly implement the functionality. A Visual Studio 2017 / 2019 environment is required for the build.
The greenlet package is of core importance for the Python ecosystem, as it implements “in-process sequential concurrent programming”, which is used for implementing asynchronous IO or service management. Greenlets pre-date native Python asyncio coroutines and are thus commonly used in applications that started off before Python 3.4 or required at some point Python 2.7 compatibility due to operating system constraints, like most OpenStack ecosystem projects. Building greenlet is a tad more complicated and all the building process is presented in the already upstreamed pull request: https://github.com/python-greenlet/greenlet/pull/224.
The pywin32 package is the core package when it comes to Windows Win32 API integration in Python. Most of the Win32 APIs are included in pywin32 package, easing Windows Python development. The build process is thoroughly explained here: https://github.com/ader1990/cloudbase-init-arm-scripts/tree/main/extra/pywin32. The fixes are already up for review upstream: https://github.com/mhammond/pywin32/pull/1617.
The cloudbase-init is a provisioning agent designed to initialize and configure guest operating systems on various cloud platforms like OpenStack, Azure, and AWS. The build and installation of cloudbase-init are fully covered in this extensive blog post: https://cloudbase.it/cloudbase-init-on-windows-arm64.
Most common packages can be used out of the box, without any code changes, whilst the scientific and DevOps packages required some level of patching to support the new architecture, as some of those packages rely heavily on C-extensions and even inline assembly code.
There’s still work to be done in order to reach an experience level similar to the Windows x64 counterpart. To begin with, package owners need to push binary wheels to Pypi to avoid the need for a local build environment and a Continuous Integration / delivery (CI / CD) infrastructure would be very useful for such purpose, stay tuned!