I’m trying to sort out the performance issue, but first things first. I notice the my local builds from source execute the “bench” suite approximately double the times as SWIP v10.1.6 (average of 2.616 vs 1.138 for run(1)). Individual results vary; for example crypt is over 5 times slower while eval is only 1.5 times slower.
Any guesses on why that might be so, or suggestions as to how to find the root cause?
AFAICT, I’m building from 10.1.11 which I believe should be comparable in performance to 10.1.6, or at least not twice as slow.
All other local changes are disabled using a CMake flag. And even those changes should only affect elementary function arithmetic and not a whole benchmark suite. It feels more like a global issue like compiler flags or a build option, but I’m not C expert.
Are you comparing against the binary distribution package? That is compiled with GCC, which produces a lot faster code for SWI-Prolog than Apple Clang. If you want full speed, install gcc. It is a Macports port (that is what I use). I guess there is also a Homebrew version. Last time I checked, the port is still gcc-15. That is a pity as gcc-16 produces even faster code.
I built your version. Needed to setup for position independent code and did some first simplification steps. Anyway, it now builds. Compiled using Fedora 44, GCC 16 on AMD RYZEN AI MAX+ 395. Normal benchmarks have exactly the same timing (they do not do any (significant) float operations). Given your f_statistics/0, I get
No crmath
101 ?- f_statistics.
Net time for base is -0.004525549999999989 sec.
Net time for sin is 0.019149918999999987 sec.
Net time for cos is 0.017953492999999987 sec.
Net time for tan is 0.023180262000000007 sec.
Net time for exp is 0.008679032000000031 sec.
Net time for log is 0.017411286000000026 sec.
with crmath
1 ?- f_statistics.
Net time for base is -0.0027924849999999835 sec.
Net time for sin is 0.049431976000000016 sec.
Net time for cos is 0.042686715999999986 sec.
Net time for tan is 0.048236589999999996 sec.
Net time for exp is 0.010941433000000028 sec.
Net time for log is 0.02389063199999994 sec.
So, roughly twice as slow. The impact on the shared object (libswipl.so) size is 2098994 → 2542553 bytes (+21%)
Thanks, that would explain it. From the ConfigureLog:
clang -cc1 version 11.0.0 (clang-1100.0.33.17) default target x86_64-apple-darwin18.7.0
I hadn’t expected 2x difference (5x on crypto). I’ll put gcc on my list.
Great; I was pretty sure I hadn’t covered all the bases. If/when the PR is processed, I’ll clone a version to continue testing.
Thanks for the data; much better than I got and closer to my expectations based on INRIA’s numbers. My size numbers for libswipl.dylib (same as .so ??):
2069760 → 2447056 (+18%).
I’m guessing that’s compiler effects well, so I’ll need to sort that out before drawing any conclusions.
It is roughly a factor 2, but depending on the CPU. You are also using a pretty old Clang version, making it even worse.
The PR is in and the results on AMD (and probably Intel) are a lot better now. After some changes to the benchmark (attached below), I now get these figures, where the last column is the comparison to the (non-crmath) reference as percentage (100 means same performance, lower numbers are better).
Use FetchContent_MakeAvailable() rather than the older deprecated function you used.
Also thanks to the above, use the library as much as possible as is. That exploits tweaks used by the library such as to enable -march=x86-64-v3 on AMD/Intel, which seems to almost double the performance. The fact that we compile the whole library is not an issue as linking a static library only imports the functions that are actually used.
Pin the version of the external GIT repo to the hash rather than main. Both for security reasons and to guarantee reproducibility. Does mean you may need to update the hash occasionally.
Those numbers are very good, and more in line with my original expectations.
So the use of FetchContent_Populate() was an AI suggestion. When the deprecation warning resulted, I asked again but it seemed to insist FetchContent_Populate was the right choice. Hallucination based on bad training data?
Anyway you ended up with a much better result so all good. I can build both versions with this, although I had to add a temporary patch since my old compiler doesn’t support -march x86-64-v3. So upgrading the compiler will be my next task to see if I can get performance numbers like yours.
There are several practical reasons your results may be better than those reported by many other users.
You tend to use Anthropic’s strongest available model, which also provides a larger context window. Claude may therefore be able to consider more project material during a session.
It may also benefit from the project-specific information accumulated in its auto-memory files under:
~/.claude/projects/<project>/memory/
Those files can preserve useful context from your extensive work with SWI-Prolog, giving Claude a stronger starting point than a user beginning with little or no saved project context.
Your prompting has also likely become more precise over the last few months. Experience with how to describe SWI-Prolog problems, provide relevant constraints, and evaluate the responses can significantly improve the quality of the results.
If you are also using the SWI-Prolog MCP, that may be another important factor. I have not yet had an opportunity to try it.
I’ve installed gcc15 via MacPorts, and tried rebuilding using option: -Dswipl_cc=/opt/local/bin/gcc-mp-15
But it still seems to be using the old clang from commandline tools. From the beginning of CMakeConfigureLog.yaml :
---
events:
-
kind: "message-v1"
backtrace:
- "/opt/local/share/cmake-3.31/Modules/CMakeDetermineSystem.cmake:205 (message)"
- "CMakeLists.txt:8 (project)"
message: |
The system is: Darwin - 18.7.0 - x86_64
-
kind: "message-v1"
backtrace:
- "/opt/local/share/cmake-3.31/Modules/CMakeDetermineCompilerId.cmake:17 (message)"
- "/opt/local/share/cmake-3.31/Modules/CMakeDetermineCompilerId.cmake:64 (__determine_compiler_id_test)"
- "/opt/local/share/cmake-3.31/Modules/CMakeDetermineCCompiler.cmake:123 (CMAKE_DETERMINE_COMPILER_ID)"
- "CMakeLists.txt:8 (project)"
message: |
Compiling the C compiler identification source file "CMakeCCompilerId.c" succeeded.
Compiler: /Library/Developer/CommandLineTools/usr/bin/cc
Build flags:
Id flags:
The output was:
0
Compilation of the C compiler identification source "CMakeCCompilerId.c" produced "a.out"
The C compiler identification is AppleClang, found in:
/Volumes/MacProHD/Developer/swipl-devel/swipl-devel-10.1.12-/build/CMakeFiles/3.31.12/CompilerIdC/a.out
and from build/CMakeFiles/3.31.12/CMakeCCcompiler.cmake :
mkdir build.gcc=15
cd build.gcc=15
../scripts/configure
The configure scripts uses the build directory name to enable or disable feaures. Run ../script/configure --help to see all defined features. That makes it easy to have several versions around. Another example
mkdir build.crmath-gcc=15
or
mkdir build.crmath-gcc=15-pgo
Which is probably what you want. Once gcc-16 is available as port we can drop the pgo as it seems to generate better code without … Apparently it is a lot better at guessing and insufficient coverage by the PGO training causes it to do worse.
So took this option and the compiler config issue seems to be sorted out. Performance test now works fine without crmath, but with it I have a new problem:
Net time for base is -0.001771000000000078 sec.
ERROR: Received fatal signal 4 (ill)
Time: Fri Jul 17 11:48:50 2026
Inferences: 4095154
Thread: 1 (main)
C-stack trace labeled "crash":
[0] save_backtrace() at /Volumes/MacProHD/Developer/swipl-devel/swipl-devel-10.1.12-/src/os/pl-cstack.c:319 [0x106e534e0]
PROLOG STACK (without arguments):
[14] elemf_test:f_time/1 [PC=19 in clause 2]
[13] elemf_test:f_timeit/2 [PC=10 in clause 1]
[12] elemf_test:f_statistics/0 [PC=36 in clause 1]
[11] $toplevel:toplevel_call/1 [PC=3 in clause 1]
[10] $toplevel:stop_backtrace/2 [PC=4 in clause 1]
[9] $tabling:$wfs_call/2 [PC=17 in clause 1]
[7] $toplevel:$execute_goal2/3 [PC=31 in clause 1]
[5] $toplevel:$query_loop/1 [PC=35 in clause 2]
[4] system:setup_call_cleanup/3 [PC=5 in clause 1]
[3] $toplevel:$query_loop/0 [PC=34 in clause 1]
PROLOG STACK (with arguments; may crash if data is corrupted):
Assertion failed: (LD->gmp.context == NULL), function PL_next_solution_guarded___LD, file /Volumes/MacProHD/Developer/swipl-devel/swipl-devel-10.1.12-/src/pl-vmi.c, line 3714.
Abort trap: 6
which is unsupported on my vintage Intel CPU. The compiler generates it because of the -march x86-64-v3 compiler option. If I use just x86-64, I get other compile time issues (Undefined symbols) with GCC.
I can do further functional testing using the Clang version and assume that anyone who wants a performant version on modern hardware can do so with the what’s currently committed (as you’ve demonstrated).
Good catch. If I use native, it builds a performant SWIP executable with GCC even if the crmath component is slow. (Seems to be even slower than clang 11 ?). And yes, it is a 64 bit CPU.
Not impossible. Clang is not a bad compiler. Its only problem is that it is bad in optimizing huge functions with complicated control. That is precisely what the virtual machine is. It can also be compiled as a set of C functions, one per instruction. It is better optimizing these small functions, but you pay the price of a function call per instruction. For Clang, the result is pretty much the same (depending on the platform).