Import Slovo 1.0.0-beta monorepo
This commit is contained in:
commit
19b0454b3f
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Rust
|
||||
/target/
|
||||
compiler/target/
|
||||
|
||||
# Build outputs
|
||||
/out/
|
||||
/build/
|
||||
*.ll
|
||||
!tests/*.ll
|
||||
*.bc
|
||||
*.o
|
||||
*.s
|
||||
|
||||
# Benchmark and local runtime artifacts
|
||||
benchmarks/**/build/
|
||||
compiler/glagol-std-layout-local-fs-alpha.txt
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Editor and OS files
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
39
.llm/MONOREPO_1_0_0_BETA.md
Normal file
39
.llm/MONOREPO_1_0_0_BETA.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Slovo Monorepo 1.0.0-beta
|
||||
|
||||
Date: 2026-05-22
|
||||
|
||||
Status: public monorepo baseline prepared from the paired Slovo design and
|
||||
Glagol compiler beta repositories.
|
||||
|
||||
## Purpose
|
||||
|
||||
Make `Hermeticum/slovo` the canonical repository for:
|
||||
|
||||
- the Slovo language contract
|
||||
- Glagol compiler implementation
|
||||
- source-authored standard library
|
||||
- runtime, examples, benchmarks, and publication documents
|
||||
|
||||
## Layout Contract
|
||||
|
||||
- language design docs: `docs/language/`
|
||||
- compiler docs: `docs/compiler/`
|
||||
- whitepapers and generated PDFs: `docs/papers/`
|
||||
- standard library source: `lib/std/`
|
||||
- compiler source and tests: `compiler/`
|
||||
- hosted runtime: `runtime/`
|
||||
- examples and benchmark harnesses: `examples/` and `benchmarks/`
|
||||
|
||||
## Beta Boundary
|
||||
|
||||
`1.0.0-beta` is a real general-purpose beta, not a stable release. It supports
|
||||
ordinary local command-line tools and libraries inside the documented beta
|
||||
surface. It does not claim stable ABI/layout, generic collections, remote
|
||||
registries, networking/async, LSP/watch/debug-adapter support, or frozen stable
|
||||
standard-library APIs.
|
||||
|
||||
## Publication Rule
|
||||
|
||||
The public repository must not contain machine-local paths, private checkout
|
||||
names, or generated PDFs that drift after regeneration. The release gate must
|
||||
fail if tracked PDFs change after `scripts/render-doc-pdfs.sh`.
|
||||
23
.llm/ROADMAP_TO_STABLE.md
Normal file
23
.llm/ROADMAP_TO_STABLE.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Roadmap From 1.0.0-beta To Stable
|
||||
|
||||
## Stable Criteria
|
||||
|
||||
Slovo reaches stable `1.0.0` only after:
|
||||
|
||||
- compatibility rules are documented and exercised across multiple beta releases
|
||||
- formatter and diagnostics schemas are frozen for promoted features
|
||||
- `lib/std` has clear API stability tiers
|
||||
- package/workspace behavior has deterministic version and dependency policy
|
||||
- conformance tests cover normal user projects, not only narrow fixtures
|
||||
- benchmark publication remains repeatable without becoming a performance claim
|
||||
|
||||
## Recommended Sequence
|
||||
|
||||
1. Harden monorepo release tooling, clean docs, and public install/build flow.
|
||||
2. Stabilize `lib/std` module boundaries and document beta-vs-stable APIs.
|
||||
3. Broaden numeric completeness with explicit `f32` and additional integer
|
||||
policy where needed.
|
||||
4. Improve collection and string breadth without exposing unstable ABI details.
|
||||
5. Expand package/workspace discipline before remote registry work.
|
||||
6. Add editor-facing diagnostics and generated documentation improvements.
|
||||
7. Freeze migration/deprecation policy and cut stable only after beta feedback.
|
||||
31
CONTRIBUTING.md
Normal file
31
CONTRIBUTING.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Contributing
|
||||
|
||||
Slovo is currently in `1.0.0-beta`. Changes should preserve the beta support
|
||||
boundary: a feature is supported only when the language docs, compiler,
|
||||
formatter, diagnostics, examples, tests, and release notes agree.
|
||||
|
||||
## Local Checks
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
cargo fmt --manifest-path compiler/Cargo.toml --check
|
||||
cargo test --manifest-path compiler/Cargo.toml
|
||||
./scripts/release-gate.sh
|
||||
```
|
||||
|
||||
`./scripts/release-gate.sh` regenerates publication PDFs and fails if tracked
|
||||
PDFs drift after regeneration.
|
||||
|
||||
## Standard Library
|
||||
|
||||
Source-authored standard-library modules live in `lib/std`. Keep imports
|
||||
explicit and keep compiler/runtime support aligned with the promoted language
|
||||
contract.
|
||||
|
||||
## Release Discipline
|
||||
|
||||
- `exp-*` labels are historical experimental milestones.
|
||||
- `1.0.0-beta` is the first real general-purpose beta.
|
||||
- `1.0.0` should wait for compatibility hardening, conformance coverage, and
|
||||
stable standard-library policy.
|
||||
10
LICENSE
Normal file
10
LICENSE
Normal file
@ -0,0 +1,10 @@
|
||||
Slovo is distributed under the terms of both the MIT license and the Apache
|
||||
License (Version 2.0).
|
||||
|
||||
Copyright (c) 2026 Sanjin Gumbarevic.
|
||||
|
||||
You may use this project under either license, at your option.
|
||||
|
||||
SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
See LICENSE-MIT and LICENSE-APACHE for full license text.
|
||||
186
LICENSE-APACHE
Normal file
186
LICENSE-APACHE
Normal file
@ -0,0 +1,186 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the
|
||||
copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other
|
||||
entities that control, are controlled by, or are under common control with
|
||||
that entity. For the purposes of this definition, "control" means (i) the
|
||||
power, direct or indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (ii) ownership of fifty percent
|
||||
(50%) or more of the outstanding shares, or (iii) beneficial ownership of
|
||||
such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation source, and
|
||||
configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object
|
||||
code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice that is
|
||||
included in or attached to the work (an example is provided in the Appendix
|
||||
below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent, as a
|
||||
whole, an original work of authorship. For the purposes of this License,
|
||||
Derivative Works shall not include works that remain separable from, or
|
||||
merely link (or bind by name) to the interfaces of, the Work and Derivative
|
||||
Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original
|
||||
version of the Work and any modifications or additions to that Work or
|
||||
Derivative Works thereof, that is intentionally submitted to Licensor for
|
||||
inclusion in the Work by the copyright owner or by an individual or Legal
|
||||
Entity authorized to submit on behalf of the copyright owner. For the
|
||||
purposes of this definition, "submitted" means any form of electronic, verbal,
|
||||
or written communication sent to the Licensor or its representatives,
|
||||
including but not limited to communication on electronic mailing lists, source
|
||||
code control systems, and issue tracking systems that are managed by, or on
|
||||
behalf of, the Licensor for the purpose of discussing and improving the Work,
|
||||
but excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on
|
||||
behalf of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
||||
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
||||
sublicense, and distribute the Work and such Derivative Works in Source or
|
||||
Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
|
||||
section) patent license to make, have made, use, offer to sell, sell, import,
|
||||
and otherwise transfer the Work, where such license applies only to those
|
||||
patent claims licensable by such Contributor that are necessarily infringed by
|
||||
their Contribution(s) alone or by combination of their Contribution(s) with
|
||||
the Work to which such Contribution(s) was submitted. If You institute patent
|
||||
litigation against any entity (including a cross-claim or counterclaim in a
|
||||
lawsuit) alleging that the Work or a Contribution incorporated within the Work
|
||||
constitutes direct or contributory patent infringement, then any patent
|
||||
licenses granted to You under this License for that Work shall terminate as
|
||||
of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or
|
||||
Derivative Works thereof in any medium, with or without modifications, and in
|
||||
Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy
|
||||
of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that
|
||||
You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You
|
||||
distribute, all copyright, patent, trademark, and attribution notices from the
|
||||
Source form of the Work, excluding those notices that do not pertain to any
|
||||
part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable copy of
|
||||
the attribution notices contained within such NOTICE file, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works, in at least
|
||||
one of the following places: within a NOTICE text file distributed as part of
|
||||
the Derivative Works; within the Source form or documentation, if provided
|
||||
along with the Derivative Works; or, within a display generated by the
|
||||
Derivative Works, if and wherever such third-party notices normally appear.
|
||||
The contents of the NOTICE file are for informational purposes only and do not
|
||||
modify the License. You may add Your own attribution notices within
|
||||
Derivative Works that You distribute, alongside or as an addendum to the
|
||||
NOTICE text from the Work, provided that such additional attribution notices
|
||||
cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a
|
||||
whole, provided Your use, reproduction, and distribution of the Work otherwise
|
||||
complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
||||
Contribution intentionally submitted for inclusion in the Work by You to the
|
||||
Licensor shall be under the terms and conditions of this License, without any
|
||||
additional terms or conditions. Notwithstanding the above, nothing herein
|
||||
shall supersede or modify the terms of any separate license agreement you may
|
||||
have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names,
|
||||
trademarks, service marks, or product names of the Licensor, except as
|
||||
required for reasonable and customary use in describing the origin of the Work
|
||||
and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
||||
writing, Licensor provides the Work (and each Contributor provides its
|
||||
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any risks
|
||||
associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in
|
||||
tort (including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed to in
|
||||
writing, shall any Contributor be liable to You for damages, including any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character arising as a result of this License or out of the use or inability
|
||||
to use the Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all other
|
||||
commercial damages or losses), even if such Contributor has been advised of
|
||||
the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work
|
||||
or Derivative Works thereof, You may choose to offer, and charge a fee for,
|
||||
acceptance of support, warranty, indemnity, or other liability obligations
|
||||
and/or rights consistent with this License. However, in accepting such
|
||||
obligations, You may act only on Your own behalf and on Your sole
|
||||
responsibility, not on behalf of any other Contributor, and only if You agree
|
||||
to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. Do not include the brackets. The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification
|
||||
within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Sanjin Gumbarevic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
88
README.md
Normal file
88
README.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Slovo
|
||||
|
||||
Slovo (ⰔⰎⰑⰂⰑ) is a typed structural programming language and toolchain.
|
||||
|
||||
This repository is the canonical public monorepo for the language design,
|
||||
standard library source, compiler, runtime, examples, benchmarks, and technical
|
||||
documents.
|
||||
|
||||
Current release: `1.0.0-beta`.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```text
|
||||
compiler/ Glagol, the first Slovo compiler
|
||||
runtime/ C runtime used by hosted native builds
|
||||
lib/std/ source-authored Slovo standard-library facades
|
||||
examples/ compiler-supported Slovo examples and projects
|
||||
benchmarks/ local benchmark comparison harnesses
|
||||
docs/language/ language manifest, specs, roadmap, and release notes
|
||||
docs/compiler/ compiler manifest, roadmap, and release notes
|
||||
docs/papers/ whitepapers and generated publication PDFs
|
||||
scripts/ local release and document tooling
|
||||
```
|
||||
|
||||
## Beta Scope
|
||||
|
||||
`1.0.0-beta` supports practical local command-line programs and libraries with:
|
||||
|
||||
- modules, explicit imports, packages, and local workspaces
|
||||
- `new`, `check`, `fmt`, `test`, `doc`, and `build`
|
||||
- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit`
|
||||
- structs, enums, fixed arrays, concrete vectors, option/result families, and
|
||||
current `match`
|
||||
- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or
|
||||
`SLOVO_STD_PATH`
|
||||
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
|
||||
|
||||
Still deferred before stable: generics, maps/sets, broad package registry
|
||||
semantics, networking/async, LSP/watch/debug-adapter guarantees, stable ABI and
|
||||
layout, and a stable standard-library compatibility freeze.
|
||||
|
||||
## Build And Test
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path compiler/Cargo.toml
|
||||
```
|
||||
|
||||
Run the full local release gate:
|
||||
|
||||
```bash
|
||||
./scripts/release-gate.sh
|
||||
```
|
||||
|
||||
Build the compiler binary:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml
|
||||
./compiler/target/debug/glagol --version
|
||||
```
|
||||
|
||||
Create and check a project:
|
||||
|
||||
```bash
|
||||
./compiler/target/debug/glagol new hello
|
||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol check hello
|
||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol test hello
|
||||
```
|
||||
|
||||
Build a native executable when Clang is available:
|
||||
|
||||
```bash
|
||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Language Manifest](docs/language/MANIFEST.md)
|
||||
- [Language Specification](docs/language/SPEC-v1.md)
|
||||
- [Compiler Manifest](docs/compiler/GLAGOL_COMPILER_MANIFEST.md)
|
||||
- [Slovo Whitepaper](docs/papers/SLOVO_WHITEPAPER.md)
|
||||
- [Glagol Whitepaper](docs/papers/GLAGOL_WHITEPAPER.md)
|
||||
|
||||
Generated PDFs live beside their Markdown sources in `docs/papers/`.
|
||||
|
||||
## License
|
||||
|
||||
Slovo is licensed under either the MIT License or the Apache License, Version
|
||||
2.0, at your option.
|
||||
1
benchmarks/array-index-loop/.gitignore
vendored
Normal file
1
benchmarks/array-index-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
72
benchmarks/array-index-loop/README.md
Normal file
72
benchmarks/array-index-loop/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Array Index Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-119`.
|
||||
|
||||
This benchmark compares immutable fixed-array indexing plus scalar accumulation
|
||||
across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/array_index_loop.c`: C comparison implementation
|
||||
- `rust/array_index_loop.rs`: Rust comparison implementation
|
||||
- `python/array_index_loop.py`: Python comparison implementation
|
||||
- `clojure/array_index_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/array_index_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `3875007` for loop count `1000000`.
|
||||
Hot-loop mode uses loop count `10000000` and checksum `38750007`. The runner
|
||||
supplies the loop count at runtime so native compilers cannot fold the loop
|
||||
into a constant answer.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/array-index-loop/run.py --list
|
||||
python3 benchmarks/array-index-loop/run.py --dry-run
|
||||
python3 benchmarks/array-index-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/array-index-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/array-index-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/array-index-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop keeps one fixed immutable `i32` array in local scope,
|
||||
indexes it with a loop-carried `% 8` position, and accumulates the selected
|
||||
value into an `i32` checksum.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/array-index-loop/benchmark.json
Normal file
10
benchmarks/array-index-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "array-index-loop",
|
||||
"source_stem": "array_index_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "3875007",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "38750007",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
33
benchmarks/array-index-loop/c/array_index_loop.c
Normal file
33
benchmarks/array-index-loop/c/array_index_loop.c
Normal file
@ -0,0 +1,33 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 3875007
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t array_index_loop(int32_t limit) {
|
||||
static const int32_t digits[8] = {3, 1, 4, 1, 5, 9, 2, 6};
|
||||
int32_t i = 0;
|
||||
int32_t acc = 7;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc + digits[i % 8];
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = array_index_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
29
benchmarks/array-index-loop/clojure/array_index_loop.clj
Normal file
29
benchmarks/array-index-loop/clojure/array_index_loop.clj
Normal file
@ -0,0 +1,29 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 3875007)
|
||||
(def digits [3 1 4 1 5 9 2 6])
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn array-index-loop [limit]
|
||||
(loop [i 0
|
||||
acc 7]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (nth digits (rem i 8)))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc)))
|
||||
|
||||
(let [result (array-index-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
@ -0,0 +1,34 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 3875007)
|
||||
(defparameter +digits+ #(3 1 4 1 5 9 2 6))
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) array-index-loop))
|
||||
(defun array-index-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 7
|
||||
while (< i limit)
|
||||
do (let* ((next (+ acc (svref +digits+ (rem i 8))))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (array-index-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
34
benchmarks/array-index-loop/python/array_index_loop.py
Normal file
34
benchmarks/array-index-loop/python/array_index_loop.py
Normal file
@ -0,0 +1,34 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 3_875_007
|
||||
DIGITS = [3, 1, 4, 1, 5, 9, 2, 6]
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def array_index_loop(limit: int) -> int:
|
||||
i = 0
|
||||
acc = 7
|
||||
|
||||
while i < limit:
|
||||
acc += DIGITS[i % 8]
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = array_index_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/array-index-loop/run.py
Normal file
13
benchmarks/array-index-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local array-index-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
40
benchmarks/array-index-loop/rust/array_index_loop.rs
Normal file
40
benchmarks/array-index-loop/rust/array_index_loop.rs
Normal file
@ -0,0 +1,40 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 3_875_007;
|
||||
const DIGITS: [i32; 8] = [3, 1, 4, 1, 5, 9, 2, 6];
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn array_index_loop(limit: i32) -> i32 {
|
||||
let mut i = 0;
|
||||
let mut acc = 7;
|
||||
|
||||
while i < limit {
|
||||
acc += DIGITS[(i % 8) as usize];
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = array_index_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/array-index-loop/slovo.toml
Normal file
4
benchmarks/array-index-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "array-index-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
60
benchmarks/array-index-loop/src/main.slo
Normal file
60
benchmarks/array-index-loop/src/main.slo
Normal file
@ -0,0 +1,60 @@
|
||||
; Benchmark scaffold fixture for local-machine array-index timing comparisons only.
|
||||
; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures.
|
||||
; The runner supplies the loop count through stdin or argv so native compilers
|
||||
; cannot fold the benchmark loop into a constant answer.
|
||||
|
||||
(module main)
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
3875007)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn values () -> (array i32 8)
|
||||
(array i32 3 1 4 1 5 9 2 6))
|
||||
|
||||
(fn array_index_loop ((limit i32)) -> i32
|
||||
(let digits (array i32 8) (values))
|
||||
(var i i32 0)
|
||||
(var acc i32 7)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (index digits (% i 8))))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (array_index_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "array index loop checksum is deterministic"
|
||||
(= (array_index_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/array-struct-field-loop/.gitignore
vendored
Normal file
1
benchmarks/array-struct-field-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
73
benchmarks/array-struct-field-loop/README.md
Normal file
73
benchmarks/array-struct-field-loop/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Array Struct Field Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-122`.
|
||||
|
||||
This benchmark compares immutable fixed-array struct-field access plus scalar
|
||||
accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on
|
||||
the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/array_struct_field_loop.c`: C comparison implementation
|
||||
- `rust/array_struct_field_loop.rs`: Rust comparison implementation
|
||||
- `python/array_struct_field_loop.py`: Python comparison implementation
|
||||
- `clojure/array_struct_field_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/array_struct_field_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `3875011` for loop count `1000000`.
|
||||
Hot-loop mode uses loop count `10000000` and checksum `38750011`. The runner
|
||||
supplies the loop count at runtime so native compilers cannot fold the loop
|
||||
into a constant answer.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/array-struct-field-loop/run.py --list
|
||||
python3 benchmarks/array-struct-field-loop/run.py --dry-run
|
||||
python3 benchmarks/array-struct-field-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/array-struct-field-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/array-struct-field-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/array-struct-field-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop keeps one immutable struct value in local scope, indexes
|
||||
the struct's fixed `i32` array field with a loop-carried `% 8` position, and
|
||||
accumulates the selected value into an `i32` checksum.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/array-struct-field-loop/benchmark.json
Normal file
10
benchmarks/array-struct-field-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "array-struct-field-loop",
|
||||
"source_stem": "array_struct_field_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "3875011",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "38750011",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 3875011
|
||||
|
||||
struct array_record {
|
||||
int32_t digits[8];
|
||||
};
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static struct array_record make_record(void) {
|
||||
return (struct array_record){.digits = {3, 1, 4, 1, 5, 9, 2, 6}};
|
||||
}
|
||||
|
||||
static int32_t array_struct_field_loop(int32_t limit) {
|
||||
struct array_record record = make_record();
|
||||
int32_t i = 0;
|
||||
int32_t acc = 11;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc + record.digits[i % 8];
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = array_struct_field_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 3875011)
|
||||
(def record {:digits [3 1 4 1 5 9 2 6]})
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn array-struct-field-loop [limit]
|
||||
(loop [i 0
|
||||
acc 11]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (nth (:digits record) (rem i 8)))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc)))
|
||||
|
||||
(let [result (array-struct-field-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
@ -0,0 +1,39 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 3875011)
|
||||
|
||||
(defstruct array-record
|
||||
(digits #(3 1 4 1 5 9 2 6) :type simple-vector))
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) array-struct-field-loop))
|
||||
(defun array-struct-field-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(let ((record (make-array-record)))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 11
|
||||
while (< i limit)
|
||||
do (let* ((digits (array-record-digits record))
|
||||
(next (+ acc (svref digits (rem i 8))))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type simple-vector digits)
|
||||
(type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc))))
|
||||
|
||||
(let ((result (array-struct-field-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
@ -0,0 +1,38 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 3_875_011
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def make_record() -> dict[str, list[int]]:
|
||||
return {"digits": [3, 1, 4, 1, 5, 9, 2, 6]}
|
||||
|
||||
|
||||
def array_struct_field_loop(limit: int) -> int:
|
||||
record = make_record()
|
||||
i = 0
|
||||
acc = 11
|
||||
|
||||
while i < limit:
|
||||
acc += record["digits"][i % 8]
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = array_struct_field_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/array-struct-field-loop/run.py
Normal file
13
benchmarks/array-struct-field-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local array-struct-field-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
@ -0,0 +1,50 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 3_875_011;
|
||||
|
||||
struct ArrayRecord {
|
||||
digits: [i32; 8],
|
||||
}
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn make_record() -> ArrayRecord {
|
||||
ArrayRecord {
|
||||
digits: [3, 1, 4, 1, 5, 9, 2, 6],
|
||||
}
|
||||
}
|
||||
|
||||
fn array_struct_field_loop(limit: i32) -> i32 {
|
||||
let record = make_record();
|
||||
let mut i = 0;
|
||||
let mut acc = 11;
|
||||
|
||||
while i < limit {
|
||||
acc += record.digits[(i % 8) as usize];
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = array_struct_field_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/array-struct-field-loop/slovo.toml
Normal file
4
benchmarks/array-struct-field-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "array-struct-field-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
71
benchmarks/array-struct-field-loop/src/main.slo
Normal file
71
benchmarks/array-struct-field-loop/src/main.slo
Normal file
@ -0,0 +1,71 @@
|
||||
; Benchmark scaffold fixture for local-machine array-struct-field timing
|
||||
; comparisons only. Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the
|
||||
; C/Rust/Python fixtures. The runner supplies the loop count through stdin or
|
||||
; argv so native compilers cannot fold the benchmark loop into a constant
|
||||
; answer.
|
||||
|
||||
(module main)
|
||||
|
||||
(struct ArrayRecord
|
||||
(digits (array i32 8))
|
||||
(wides (array i64 2))
|
||||
(ratios (array f64 3))
|
||||
(flags (array bool 3))
|
||||
(words (array string 3)))
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
3875011)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn make_record () -> ArrayRecord
|
||||
(ArrayRecord (digits (array i32 3 1 4 1 5 9 2 6)) (wides (array i64 40i64 41i64)) (ratios (array f64 1.5 2.5 3.5)) (flags (array bool false true true)) (words (array string "sun" "moon" "star"))))
|
||||
|
||||
(fn int_at ((record ArrayRecord) (i i32)) -> i32
|
||||
(index (. record digits) i))
|
||||
|
||||
(fn array_struct_field_loop ((limit i32)) -> i32
|
||||
(let record ArrayRecord (make_record))
|
||||
(var i i32 0)
|
||||
(var acc i32 11)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (int_at record (% i 8))))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (array_struct_field_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "array struct field loop checksum is deterministic"
|
||||
(= (array_struct_field_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/branch-loop/.gitignore
vendored
Normal file
1
benchmarks/branch-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
30
benchmarks/branch-loop/README.md
Normal file
30
benchmarks/branch-loop/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Branch Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-40`; Common Lisp/SBCL comparison added by `exp-41`; hot-loop
|
||||
mode added by `exp-42`.
|
||||
|
||||
This benchmark compares a deterministic branch-heavy integer loop across
|
||||
Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
All implementations print checksum `1185071` for loop count `1000000`.
|
||||
Hot-loop mode uses loop count `10000000` and checksum `220775`. The runner
|
||||
supplies the loop count at runtime.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It reports total time plus
|
||||
normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/branch-loop/benchmark.json
Normal file
10
benchmarks/branch-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "branch-loop",
|
||||
"source_stem": "branch_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "1185071",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "220775",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
33
benchmarks/branch-loop/c/branch_loop.c
Normal file
33
benchmarks/branch-loop/c/branch_loop.c
Normal file
@ -0,0 +1,33 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 1185071
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t branch_loop(int32_t limit) {
|
||||
int32_t i = 0;
|
||||
int32_t acc = 7;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
acc = i < 500000 ? acc + 3 : acc + 7;
|
||||
acc = acc > 1234567 ? acc - 1234567 : acc + 11;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = branch_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
33
benchmarks/branch-loop/clojure/branch_loop.clj
Normal file
33
benchmarks/branch-loop/clojure/branch_loop.clj
Normal file
@ -0,0 +1,33 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 1185071)
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn branch-loop [limit]
|
||||
(loop [i 0
|
||||
acc 7]
|
||||
(if (< i limit)
|
||||
(let [bounded (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc)
|
||||
branched (if (< i 500000)
|
||||
(+ bounded 3)
|
||||
(+ bounded 7))
|
||||
next (if (> branched 1234567)
|
||||
(- branched 1234567)
|
||||
(+ branched 11))]
|
||||
(recur (inc i) next))
|
||||
acc)))
|
||||
|
||||
(let [result (branch-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
38
benchmarks/branch-loop/common-lisp/branch_loop.lisp
Normal file
38
benchmarks/branch-loop/common-lisp/branch_loop.lisp
Normal file
@ -0,0 +1,38 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 1185071)
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) branch-loop))
|
||||
(defun branch-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 7
|
||||
while (< i limit)
|
||||
do (let* ((bounded (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(branched (if (< i 500000)
|
||||
(+ bounded 3)
|
||||
(+ bounded 7)))
|
||||
(next (if (> branched 1234567)
|
||||
(- branched 1234567)
|
||||
(+ branched 11))))
|
||||
(declare (type fixnum bounded branched next))
|
||||
(setf acc next
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (branch-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
34
benchmarks/branch-loop/python/branch_loop.py
Normal file
34
benchmarks/branch-loop/python/branch_loop.py
Normal file
@ -0,0 +1,34 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 1_185_071
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def branch_loop(limit: int) -> int:
|
||||
i = 0
|
||||
acc = 7
|
||||
|
||||
while i < limit:
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
acc = acc + 3 if i < 500_000 else acc + 7
|
||||
acc = acc - 1_234_567 if acc > 1_234_567 else acc + 11
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = branch_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/branch-loop/run.py
Normal file
13
benchmarks/branch-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local branch-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
44
benchmarks/branch-loop/rust/branch_loop.rs
Normal file
44
benchmarks/branch-loop/rust/branch_loop.rs
Normal file
@ -0,0 +1,44 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 1_185_071;
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn branch_loop(limit: i32) -> i32 {
|
||||
let mut i = 0;
|
||||
let mut acc = 7;
|
||||
|
||||
while i < limit {
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
acc = if i < 500_000 { acc + 3 } else { acc + 7 };
|
||||
acc = if acc > 1_234_567 {
|
||||
acc - 1_234_567
|
||||
} else {
|
||||
acc + 11
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = branch_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/branch-loop/slovo.toml
Normal file
4
benchmarks/branch-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "branch-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
58
benchmarks/branch-loop/src/main.slo
Normal file
58
benchmarks/branch-loop/src/main.slo
Normal file
@ -0,0 +1,58 @@
|
||||
; Benchmark scaffold fixture for local-machine branch timing comparisons only.
|
||||
|
||||
(module main)
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
1185071)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn branch_loop ((limit i32)) -> i32
|
||||
(var i i32 0)
|
||||
(var acc i32 7)
|
||||
(while (< i limit)
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set acc (if (< i 500000)
|
||||
(+ acc 3)
|
||||
(+ acc 7)))
|
||||
(set acc (if (> acc 1234567)
|
||||
(- acc 1234567)
|
||||
(+ acc 11)))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (branch_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "branch loop checksum is deterministic"
|
||||
(= (branch_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/enum-struct-payload-loop/.gitignore
vendored
Normal file
1
benchmarks/enum-struct-payload-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
73
benchmarks/enum-struct-payload-loop/README.md
Normal file
73
benchmarks/enum-struct-payload-loop/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Enum Struct Payload Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-122`.
|
||||
|
||||
This benchmark compares enum payload selection, enum `match`, and immutable
|
||||
fixed-array access through a struct payload plus scalar accumulation across
|
||||
Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/enum_struct_payload_loop.c`: C comparison implementation
|
||||
- `rust/enum_struct_payload_loop.rs`: Rust comparison implementation
|
||||
- `python/enum_struct_payload_loop.py`: Python comparison implementation
|
||||
- `clojure/enum_struct_payload_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/enum_struct_payload_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `3500013` for loop count `1000000`.
|
||||
Hot-loop mode uses loop count `10000000` and checksum `35000013`. The runner
|
||||
supplies the loop count at runtime so native compilers cannot fold the loop
|
||||
into a constant answer.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --list
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --dry-run
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/enum-struct-payload-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop alternates between two enum payload variants, matches the
|
||||
selected variant, reads one fixed `i32` array through the bound struct
|
||||
payload, and accumulates the selected value into an `i32` checksum.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/enum-struct-payload-loop/benchmark.json
Normal file
10
benchmarks/enum-struct-payload-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "enum-struct-payload-loop",
|
||||
"source_stem": "enum_struct_payload_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "3500013",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "35000013",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 3500013
|
||||
|
||||
typedef struct {
|
||||
int32_t digits[8];
|
||||
} Packet;
|
||||
|
||||
typedef enum {
|
||||
PACKET_MISSING = 0,
|
||||
PACKET_LIVE = 1,
|
||||
PACKET_CACHED = 2,
|
||||
} PacketTag;
|
||||
|
||||
typedef struct {
|
||||
PacketTag tag;
|
||||
Packet payload;
|
||||
} PacketState;
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static PacketState live_state(void) {
|
||||
PacketState state = {PACKET_LIVE, {{2, 7, 1, 8, 2, 8, 1, 8}}};
|
||||
return state;
|
||||
}
|
||||
|
||||
static PacketState cached_state(void) {
|
||||
PacketState state = {PACKET_CACHED, {{1, 6, 1, 8, 0, 3, 4, 5}}};
|
||||
return state;
|
||||
}
|
||||
|
||||
static PacketState select_state(int32_t i, PacketState live, PacketState cached) {
|
||||
return i % 2 == 0 ? live : cached;
|
||||
}
|
||||
|
||||
static int32_t state_digit(PacketState state, int32_t index) {
|
||||
switch (state.tag) {
|
||||
case PACKET_LIVE:
|
||||
case PACKET_CACHED:
|
||||
return state.payload.digits[index];
|
||||
case PACKET_MISSING:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t enum_struct_payload_loop(int32_t limit) {
|
||||
PacketState live = live_state();
|
||||
PacketState cached = cached_state();
|
||||
int32_t i = 0;
|
||||
int32_t acc = 13;
|
||||
|
||||
while (i < limit) {
|
||||
PacketState state = select_state(i, live, cached);
|
||||
acc = acc + state_digit(state, i % 8);
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = enum_struct_payload_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 3500013)
|
||||
|
||||
(defrecord Packet [digits])
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn live-state []
|
||||
{:tag :live :payload (->Packet [2 7 1 8 2 8 1 8])})
|
||||
|
||||
(defn cached-state []
|
||||
{:tag :cached :payload (->Packet [1 6 1 8 0 3 4 5])})
|
||||
|
||||
(defn select-state [i live cached]
|
||||
(if (zero? (rem i 2)) live cached))
|
||||
|
||||
(defn state-digit [state index]
|
||||
(case (:tag state)
|
||||
:missing 0
|
||||
(nth (:digits ^Packet (:payload state)) index)))
|
||||
|
||||
(defn enum-struct-payload-loop [limit]
|
||||
(loop [i 0
|
||||
acc 13
|
||||
live (live-state)
|
||||
cached (cached-state)]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (state-digit (select-state i live cached) (rem i 8)))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded live cached))
|
||||
acc)))
|
||||
|
||||
(let [result (enum-struct-payload-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
@ -0,0 +1,66 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 3500013)
|
||||
|
||||
(defstruct (packet (:constructor %make-packet (digits))) digits)
|
||||
(defstruct (packet-state (:constructor %make-packet-state (tag payload))) tag payload)
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function () packet-state) live-state))
|
||||
(defun live-state ()
|
||||
(%make-packet-state :live (%make-packet #(2 7 1 8 2 8 1 8))))
|
||||
|
||||
(declaim (ftype (function () packet-state) cached-state))
|
||||
(defun cached-state ()
|
||||
(%make-packet-state :cached (%make-packet #(1 6 1 8 0 3 4 5))))
|
||||
|
||||
(declaim (ftype (function (fixnum packet-state packet-state) packet-state) select-state))
|
||||
(defun select-state (i live cached)
|
||||
(declare (type fixnum i))
|
||||
(if (zerop (rem i 2))
|
||||
live
|
||||
cached))
|
||||
|
||||
(declaim (ftype (function (packet-state fixnum) fixnum) state-digit))
|
||||
(defun state-digit (state index)
|
||||
(declare (type packet-state state)
|
||||
(type fixnum index))
|
||||
(let* ((payload (packet-state-payload state))
|
||||
(digits (packet-digits payload))
|
||||
(value (svref digits index)))
|
||||
(declare (type simple-vector digits)
|
||||
(type fixnum value))
|
||||
(case (packet-state-tag state)
|
||||
(:missing 0)
|
||||
(otherwise value))))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) enum-struct-payload-loop))
|
||||
(defun enum-struct-payload-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 13
|
||||
with live of-type packet-state = (live-state)
|
||||
with cached of-type packet-state = (cached-state)
|
||||
while (< i limit)
|
||||
do (let* ((next (+ acc (state-digit (select-state i live cached) (rem i 8))))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (enum-struct-payload-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
@ -0,0 +1,50 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 3_500_013
|
||||
|
||||
LIVE_STATE = {"tag": "live", "payload": {"digits": [2, 7, 1, 8, 2, 8, 1, 8]}}
|
||||
CACHED_STATE = {"tag": "cached", "payload": {"digits": [1, 6, 1, 8, 0, 3, 4, 5]}}
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def select_state(i: int) -> dict[str, object]:
|
||||
return LIVE_STATE if i % 2 == 0 else CACHED_STATE
|
||||
|
||||
|
||||
def state_digit(state: dict[str, object], index: int) -> int:
|
||||
if state["tag"] == "missing":
|
||||
return 0
|
||||
payload = state["payload"]
|
||||
assert isinstance(payload, dict)
|
||||
digits = payload["digits"]
|
||||
assert isinstance(digits, list)
|
||||
return digits[index]
|
||||
|
||||
|
||||
def enum_struct_payload_loop(limit: int) -> int:
|
||||
i = 0
|
||||
acc = 13
|
||||
|
||||
while i < limit:
|
||||
acc += state_digit(select_state(i), i % 8)
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = enum_struct_payload_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/enum-struct-payload-loop/run.py
Normal file
13
benchmarks/enum-struct-payload-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local enum-struct-payload-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
@ -0,0 +1,78 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 3_500_013;
|
||||
|
||||
struct Packet {
|
||||
digits: [i32; 8],
|
||||
}
|
||||
|
||||
enum PacketState {
|
||||
Missing,
|
||||
Live(Packet),
|
||||
Cached(Packet),
|
||||
}
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn live_state() -> PacketState {
|
||||
PacketState::Live(Packet {
|
||||
digits: [2, 7, 1, 8, 2, 8, 1, 8],
|
||||
})
|
||||
}
|
||||
|
||||
fn cached_state() -> PacketState {
|
||||
PacketState::Cached(Packet {
|
||||
digits: [1, 6, 1, 8, 0, 3, 4, 5],
|
||||
})
|
||||
}
|
||||
|
||||
fn select_state<'a>(i: i32, live: &'a PacketState, cached: &'a PacketState) -> &'a PacketState {
|
||||
if i % 2 == 0 {
|
||||
live
|
||||
} else {
|
||||
cached
|
||||
}
|
||||
}
|
||||
|
||||
fn state_digit(state: &PacketState, index: i32) -> i32 {
|
||||
match state {
|
||||
PacketState::Missing => 0,
|
||||
PacketState::Live(payload) | PacketState::Cached(payload) => payload.digits[index as usize],
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_struct_payload_loop(limit: i32) -> i32 {
|
||||
let live = live_state();
|
||||
let cached = cached_state();
|
||||
let mut i = 0;
|
||||
let mut acc = 13;
|
||||
|
||||
while i < limit {
|
||||
acc += state_digit(select_state(i, &live, &cached), i % 8);
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = enum_struct_payload_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/enum-struct-payload-loop/slovo.toml
Normal file
4
benchmarks/enum-struct-payload-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "enum-struct-payload-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
93
benchmarks/enum-struct-payload-loop/src/main.slo
Normal file
93
benchmarks/enum-struct-payload-loop/src/main.slo
Normal file
@ -0,0 +1,93 @@
|
||||
; Benchmark scaffold fixture for local-machine enum-struct-payload timing
|
||||
; comparisons only. Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the
|
||||
; C/Rust/Python fixtures. The runner supplies the loop count through stdin or
|
||||
; argv so native compilers cannot fold the benchmark loop into a constant
|
||||
; answer.
|
||||
|
||||
(module main)
|
||||
|
||||
(struct Packet
|
||||
(digits (array i32 8)))
|
||||
|
||||
(enum PacketState
|
||||
Missing
|
||||
(Live Packet)
|
||||
(Cached Packet))
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
3500013)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn live_digits () -> (array i32 8)
|
||||
(array i32 2 7 1 8 2 8 1 8))
|
||||
|
||||
(fn cached_digits () -> (array i32 8)
|
||||
(array i32 1 6 1 8 0 3 4 5))
|
||||
|
||||
(fn live_state () -> PacketState
|
||||
(PacketState.Live (Packet (digits (live_digits)))))
|
||||
|
||||
(fn cached_state () -> PacketState
|
||||
(PacketState.Cached (Packet (digits (cached_digits)))))
|
||||
|
||||
(fn select_state ((i i32) (live PacketState) (cached PacketState)) -> PacketState
|
||||
(if (= (% i 2) 0)
|
||||
live
|
||||
cached))
|
||||
|
||||
(fn state_digit ((state PacketState) (i i32)) -> i32
|
||||
(match state
|
||||
((PacketState.Missing)
|
||||
0)
|
||||
((PacketState.Live payload)
|
||||
(index (. payload digits) i))
|
||||
((PacketState.Cached payload)
|
||||
(index (. payload digits) i))))
|
||||
|
||||
(fn enum_struct_payload_loop ((limit i32)) -> i32
|
||||
(let live PacketState (live_state))
|
||||
(let cached PacketState (cached_state))
|
||||
(var i i32 0)
|
||||
(var acc i32 13)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (state_digit (select_state i live cached) (% i 8))))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (enum_struct_payload_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "enum struct payload loop checksum is deterministic"
|
||||
(= (enum_struct_payload_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/math-loop/.gitignore
vendored
Normal file
1
benchmarks/math-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
72
benchmarks/math-loop/README.md
Normal file
72
benchmarks/math-loop/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Math Loop Benchmark Scaffold
|
||||
|
||||
Release: established by `exp-39`; Clojure comparison and shared runner
|
||||
updated by `exp-40`; Common Lisp/SBCL comparison added by `exp-41`;
|
||||
hot-loop mode added by `exp-42`.
|
||||
|
||||
This directory is a local timing harness for comparing one deterministic
|
||||
math-heavy loop across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL
|
||||
on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/math_loop.c`: C comparison implementation
|
||||
- `rust/math_loop.rs`: Rust comparison implementation
|
||||
- `python/math_loop.py`: Python comparison implementation
|
||||
- `clojure/math_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/math_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print the same checksum, `5000001`, for the default loop
|
||||
count, `1000000`. Hot-loop mode uses loop count `10000000` and checksum
|
||||
`50000001`. The runner supplies the loop count at runtime so native compilers
|
||||
cannot fold the loop into a constant answer.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/math-loop/run.py --list
|
||||
python3 benchmarks/math-loop/run.py --dry-run
|
||||
python3 benchmarks/math-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/math-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/math-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/math-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/math-loop/benchmark.json
Normal file
10
benchmarks/math-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "math-loop",
|
||||
"source_stem": "math_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "5000001",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "50000001",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
32
benchmarks/math-loop/c/math_loop.c
Normal file
32
benchmarks/math-loop/c/math_loop.c
Normal file
@ -0,0 +1,32 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 5000001
|
||||
|
||||
static int32_t math_loop(int32_t limit) {
|
||||
int32_t i = 0;
|
||||
int32_t acc = 1;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc + ((i + 3) * 2);
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = math_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
28
benchmarks/math-loop/clojure/math_loop.clj
Normal file
28
benchmarks/math-loop/clojure/math_loop.clj
Normal file
@ -0,0 +1,28 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 5000001)
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn math-loop [limit]
|
||||
(loop [i 0
|
||||
acc 1]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (* (+ i 3) 2))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc)))
|
||||
|
||||
(let [result (math-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
33
benchmarks/math-loop/common-lisp/math_loop.lisp
Normal file
33
benchmarks/math-loop/common-lisp/math_loop.lisp
Normal file
@ -0,0 +1,33 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 5000001)
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) math-loop))
|
||||
(defun math-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 1
|
||||
while (< i limit)
|
||||
do (let* ((next (+ acc (* (+ i 3) 2)))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (math-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
33
benchmarks/math-loop/python/math_loop.py
Normal file
33
benchmarks/math-loop/python/math_loop.py
Normal file
@ -0,0 +1,33 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 5_000_001
|
||||
|
||||
|
||||
def math_loop(limit: int) -> int:
|
||||
i = 0
|
||||
acc = 1
|
||||
|
||||
while i < limit:
|
||||
acc = acc + ((i + 3) * 2)
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = math_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/math-loop/run.py
Normal file
13
benchmarks/math-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local math-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
39
benchmarks/math-loop/rust/math_loop.rs
Normal file
39
benchmarks/math-loop/rust/math_loop.rs
Normal file
@ -0,0 +1,39 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 5_000_001;
|
||||
|
||||
fn math_loop(limit: i32) -> i32 {
|
||||
let mut i = 0;
|
||||
let mut acc = 1;
|
||||
|
||||
while i < limit {
|
||||
acc = acc + ((i + 3) * 2);
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = math_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/math-loop/slovo.toml
Normal file
4
benchmarks/math-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "math-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
56
benchmarks/math-loop/src/main.slo
Normal file
56
benchmarks/math-loop/src/main.slo
Normal file
@ -0,0 +1,56 @@
|
||||
; Benchmark scaffold fixture for local-machine timing comparisons only.
|
||||
; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures.
|
||||
; The runner supplies the loop count through stdin so native compilers cannot
|
||||
; fold the benchmark loop into a constant answer.
|
||||
|
||||
(module main)
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
5000001)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn math_loop ((limit i32)) -> i32
|
||||
(var i i32 0)
|
||||
(var acc i32 1)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (* (+ i 3) 2)))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (math_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "math loop checksum is deterministic"
|
||||
(= (math_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/parse-loop/.gitignore
vendored
Normal file
1
benchmarks/parse-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
34
benchmarks/parse-loop/README.md
Normal file
34
benchmarks/parse-loop/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Parse Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-40`; Common Lisp/SBCL comparison added by `exp-41`; hot-loop
|
||||
mode added by `exp-42`.
|
||||
|
||||
This benchmark compares repeated signed decimal `i32` parsing across Slovo, C,
|
||||
Rust, Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
All implementations print checksum `345000001` for loop count `1000000` and
|
||||
parse text `12345`. Hot-loop mode uses loop count `10000000` and checksum
|
||||
`450000001`. The runner supplies the loop count and parse text at runtime.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The parse implementations are intentionally comparable by input and checksum,
|
||||
not identical by parser internals: Slovo uses `std.string.parse_i32_result`,
|
||||
C uses `strtol`, Rust uses `parse::<i32>()`, Python uses `int`, Clojure uses
|
||||
`Integer/parseInt`, and Common Lisp uses `parse-integer`.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It reports total time plus
|
||||
normalized time for the base `1000000` loop count.
|
||||
11
benchmarks/parse-loop/benchmark.json
Normal file
11
benchmarks/parse-loop/benchmark.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"benchmark": "parse-loop",
|
||||
"source_stem": "parse_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "345000001",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "450000001",
|
||||
"hot_stdin": "10000000\n",
|
||||
"run_args": ["12345"]
|
||||
}
|
||||
34
benchmarks/parse-loop/c/parse_loop.c
Normal file
34
benchmarks/parse-loop/c/parse_loop.c
Normal file
@ -0,0 +1,34 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 345000001
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t parse_loop(int32_t limit, const char *text) {
|
||||
int32_t i = 0;
|
||||
int32_t acc = 1;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc + (int32_t)strtol(text, NULL, 10);
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *text = argc > 1 ? argv[1] : "12345";
|
||||
int32_t result = parse_loop(configured_loop_count(), text);
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
31
benchmarks/parse-loop/clojure/parse_loop.clj
Normal file
31
benchmarks/parse-loop/clojure/parse_loop.clj
Normal file
@ -0,0 +1,31 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 345000001)
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn parse-text []
|
||||
(or (first *command-line-args*) "12345"))
|
||||
|
||||
(defn parse-loop [limit text]
|
||||
(loop [i 0
|
||||
acc 1]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (Integer/parseInt ^String text))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc)))
|
||||
|
||||
(let [result (parse-loop (configured-loop-count) (parse-text))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
38
benchmarks/parse-loop/common-lisp/parse_loop.lisp
Normal file
38
benchmarks/parse-loop/common-lisp/parse_loop.lisp
Normal file
@ -0,0 +1,38 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 345000001)
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function () string) parse-text))
|
||||
(defun parse-text ()
|
||||
(or (second sb-ext:*posix-argv*) "12345"))
|
||||
|
||||
(declaim (ftype (function (fixnum string) fixnum) parse-loop))
|
||||
(defun parse-loop (limit text)
|
||||
(declare (type fixnum limit)
|
||||
(type string text))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 1
|
||||
while (< i limit)
|
||||
do (let* ((next (+ acc (parse-integer text)))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (parse-loop (configured-loop-count) (parse-text))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
40
benchmarks/parse-loop/python/parse_loop.py
Normal file
40
benchmarks/parse-loop/python/parse_loop.py
Normal file
@ -0,0 +1,40 @@
|
||||
import sys
|
||||
|
||||
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 345_000_001
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def parse_text() -> str:
|
||||
return sys.argv[1] if len(sys.argv) > 1 else "12345"
|
||||
|
||||
|
||||
def parse_loop(limit: int, text: str) -> int:
|
||||
i = 0
|
||||
acc = 1
|
||||
|
||||
while i < limit:
|
||||
acc += int(text)
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = parse_loop(configured_loop_count(), parse_text())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/parse-loop/run.py
Normal file
13
benchmarks/parse-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local parse-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
44
benchmarks/parse-loop/rust/parse_loop.rs
Normal file
44
benchmarks/parse-loop/rust/parse_loop.rs
Normal file
@ -0,0 +1,44 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 345_000_001;
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn parse_text() -> String {
|
||||
std::env::args().nth(1).unwrap_or_else(|| "12345".to_string())
|
||||
}
|
||||
|
||||
fn parse_loop(limit: i32, text: &str) -> i32 {
|
||||
let mut i = 0;
|
||||
let mut acc = 1;
|
||||
|
||||
while i < limit {
|
||||
acc += text.parse::<i32>().unwrap();
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let text = parse_text();
|
||||
let result = parse_loop(configured_loop_count(), &text);
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/parse-loop/slovo.toml
Normal file
4
benchmarks/parse-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "parse-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
59
benchmarks/parse-loop/src/main.slo
Normal file
59
benchmarks/parse-loop/src/main.slo
Normal file
@ -0,0 +1,59 @@
|
||||
; Benchmark scaffold fixture for local-machine parse timing comparisons only.
|
||||
|
||||
(module main)
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
345000001)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 2)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn parse_text () -> string
|
||||
(std.process.arg 1))
|
||||
|
||||
(fn parsed_value ((text string)) -> i32
|
||||
(unwrap_ok (std.string.parse_i32_result text)))
|
||||
|
||||
(fn parse_loop ((limit i32) (text string)) -> i32
|
||||
(var i i32 0)
|
||||
(var acc i32 1)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (parsed_value text)))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (parse_loop (configured_loop_count) (parse_text)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "parse loop checksum is deterministic"
|
||||
(= (parse_loop (loop_count) "12345") (expected_checksum)))
|
||||
522
benchmarks/runner.py
Normal file
522
benchmarks/runner.py
Normal file
@ -0,0 +1,522 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared local benchmark runner for Glagol benchmark scaffolds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
|
||||
TIMING_SCOPE = "local-machine comparison only"
|
||||
TIMING_MODES = ["cold-process", "hot-loop"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BenchmarkSpec:
|
||||
name: str
|
||||
source_stem: str
|
||||
loop_count: int
|
||||
expected_checksum: str
|
||||
stdin_text: str
|
||||
hot_loop_count: int
|
||||
hot_expected_checksum: str
|
||||
hot_stdin_text: str
|
||||
run_args: list[str]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RunParameters:
|
||||
mode: str
|
||||
loop_count: int
|
||||
expected_checksum: str
|
||||
stdin_text: str
|
||||
base_loop_count: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Implementation:
|
||||
name: str
|
||||
language: str
|
||||
source: Path
|
||||
build: Callable[[Path, BenchmarkSpec, argparse.Namespace], tuple[list[str], list[str] | None]]
|
||||
run: Callable[[Path, BenchmarkSpec, argparse.Namespace], list[str]]
|
||||
|
||||
|
||||
def main(root: Path, argv: list[str]) -> int:
|
||||
spec = read_spec(root)
|
||||
implementations = available_implementations(root, spec)
|
||||
parser = argparse.ArgumentParser(description=f"Run local {spec.name} timing comparisons.")
|
||||
parser.add_argument("--list", action="store_true", help="print benchmark metadata and exit")
|
||||
parser.add_argument("--json", action="store_true", help="emit JSON for --list or results")
|
||||
parser.add_argument("--dry-run", action="store_true", help="print planned commands without running them")
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=TIMING_MODES,
|
||||
default="cold-process",
|
||||
help="cold-process measures one normal run; hot-loop uses an amplified loop count and normalized results",
|
||||
)
|
||||
parser.add_argument("--only", choices=[impl.name for impl in implementations], action="append")
|
||||
parser.add_argument("--repeats", type=positive_int, default=5)
|
||||
parser.add_argument("--warmups", type=non_negative_int, default=1)
|
||||
parser.add_argument("--glagol", help="path to the glagol compiler binary")
|
||||
parser.add_argument("--cc", help="path to the C compiler")
|
||||
parser.add_argument("--clojure", help="path to the clojure command")
|
||||
parser.add_argument("--clojure-jar", help="path to a clojure jar for `java -cp ... clojure.main`")
|
||||
parser.add_argument("--sbcl", help="path to the SBCL executable for Common Lisp comparisons")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
selected = select_implementations(implementations, args.only)
|
||||
|
||||
if args.list:
|
||||
emit_list(root, spec, selected, args.json)
|
||||
return 0
|
||||
|
||||
if args.dry_run:
|
||||
emit_dry_run(root, spec, selected, args)
|
||||
return 0
|
||||
|
||||
results = run_benchmarks(root, spec, selected, args)
|
||||
emit_results(spec, results, args.json)
|
||||
return 1 if any(result["status"] == "failed" for result in results) else 0
|
||||
|
||||
|
||||
def read_spec(root: Path) -> BenchmarkSpec:
|
||||
data = json.loads((root / "benchmark.json").read_text(encoding="utf-8"))
|
||||
loop_count = int(data["loop_count"])
|
||||
return BenchmarkSpec(
|
||||
name=str(data["benchmark"]),
|
||||
source_stem=str(data["source_stem"]),
|
||||
loop_count=loop_count,
|
||||
expected_checksum=str(data["expected_checksum"]),
|
||||
stdin_text=str(data.get("stdin", f"{loop_count}\n")),
|
||||
hot_loop_count=int(data.get("hot_loop_count", loop_count)),
|
||||
hot_expected_checksum=str(data.get("hot_expected_checksum", data["expected_checksum"])),
|
||||
hot_stdin_text=str(data.get("hot_stdin", f"{int(data.get('hot_loop_count', loop_count))}\n")),
|
||||
run_args=[str(item) for item in data.get("run_args", [])],
|
||||
)
|
||||
|
||||
|
||||
def available_implementations(root: Path, spec: BenchmarkSpec) -> list[Implementation]:
|
||||
candidates = [
|
||||
Implementation("slovo", "Slovo", root / "src" / "main.slo", slovo_build, slovo_run),
|
||||
Implementation("c", "C", root / "c" / f"{spec.source_stem}.c", c_build, c_run),
|
||||
Implementation("rust", "Rust", root / "rust" / f"{spec.source_stem}.rs", rust_build, rust_run),
|
||||
Implementation("python", "Python", root / "python" / f"{spec.source_stem}.py", python_build, python_run),
|
||||
Implementation("clojure", "Clojure", root / "clojure" / f"{spec.source_stem}.clj", clojure_build, clojure_run),
|
||||
Implementation(
|
||||
"common_lisp",
|
||||
"Common Lisp (SBCL)",
|
||||
root / "common-lisp" / f"{spec.source_stem}.lisp",
|
||||
common_lisp_build,
|
||||
common_lisp_run,
|
||||
),
|
||||
]
|
||||
return [impl for impl in candidates if impl.source.is_file()]
|
||||
|
||||
|
||||
def slovo_compiler(root: Path, args: argparse.Namespace) -> str | None:
|
||||
if args.glagol:
|
||||
return args.glagol
|
||||
env_path = os.environ.get("GLAGOL")
|
||||
if env_path:
|
||||
return env_path
|
||||
candidate = root.parents[1] / "compiler" / "target" / "debug" / executable("glagol")
|
||||
if candidate.is_file():
|
||||
return str(candidate)
|
||||
return shutil.which("glagol")
|
||||
|
||||
|
||||
def executable(name: str) -> str:
|
||||
return f"{name}.exe" if os.name == "nt" else name
|
||||
|
||||
|
||||
def slovo_build(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
compiler = slovo_compiler(root, args)
|
||||
if compiler is None:
|
||||
return [], ["missing glagol compiler; set GLAGOL or pass --glagol"]
|
||||
if os.environ.get("GLAGOL_CLANG") is None and shutil.which("clang") is None:
|
||||
return [], ["missing clang for glagol build; set GLAGOL_CLANG"]
|
||||
output = build_dir(root) / executable(f"slovo-{spec.name}")
|
||||
return [compiler, "build", str(root), "-o", str(output)], None
|
||||
|
||||
|
||||
def slovo_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]:
|
||||
params = run_parameters(spec, _args.mode)
|
||||
return [str(build_dir(root) / executable(f"slovo-{spec.name}")), *spec.run_args, str(params.loop_count)]
|
||||
|
||||
|
||||
def c_build(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
compiler = args.cc or os.environ.get("CC") or first_available(["clang", "cc", "gcc"])
|
||||
if compiler is None:
|
||||
return [], ["missing C compiler; set CC or pass --cc"]
|
||||
output = build_dir(root) / executable(f"c-{spec.name}")
|
||||
return [compiler, "-O2", "-std=c11", str(root / "c" / f"{spec.source_stem}.c"), "-o", str(output)], None
|
||||
|
||||
|
||||
def c_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]:
|
||||
return [str(build_dir(root) / executable(f"c-{spec.name}")), *spec.run_args]
|
||||
|
||||
|
||||
def rust_build(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
rustc = first_available(["rustc"])
|
||||
if rustc is None:
|
||||
return [], ["missing rustc"]
|
||||
output = build_dir(root) / executable(f"rust-{spec.name}")
|
||||
return [
|
||||
rustc,
|
||||
"-C",
|
||||
"opt-level=3",
|
||||
"-C",
|
||||
"debuginfo=0",
|
||||
str(root / "rust" / f"{spec.source_stem}.rs"),
|
||||
"-o",
|
||||
str(output),
|
||||
], None
|
||||
|
||||
|
||||
def rust_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]:
|
||||
return [str(build_dir(root) / executable(f"rust-{spec.name}")), *spec.run_args]
|
||||
|
||||
|
||||
def python_build(_root: Path, _spec: BenchmarkSpec, _args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
return [], None
|
||||
|
||||
|
||||
def python_run(root: Path, spec: BenchmarkSpec, _args: argparse.Namespace) -> list[str]:
|
||||
return [sys.executable, str(root / "python" / f"{spec.source_stem}.py"), *spec.run_args]
|
||||
|
||||
|
||||
def clojure_build(_root: Path, _spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
if clojure_command(args) is None:
|
||||
return [], ["missing clojure command; set CLOJURE, pass --clojure, or set CLOJURE_JAR"]
|
||||
return [], None
|
||||
|
||||
|
||||
def clojure_run(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> list[str]:
|
||||
source = str(root / "clojure" / f"{spec.source_stem}.clj")
|
||||
command = clojure_command(args)
|
||||
assert command is not None
|
||||
return [*command, source, *spec.run_args]
|
||||
|
||||
|
||||
def clojure_command(args: argparse.Namespace) -> list[str] | None:
|
||||
if args.clojure:
|
||||
return [args.clojure]
|
||||
env_path = os.environ.get("CLOJURE")
|
||||
if env_path:
|
||||
return [env_path]
|
||||
found = shutil.which("clojure")
|
||||
if found:
|
||||
return [found]
|
||||
|
||||
jar = args.clojure_jar or os.environ.get("CLOJURE_JAR")
|
||||
java = shutil.which("java")
|
||||
if jar and java:
|
||||
return [java, "-cp", jar, "clojure.main"]
|
||||
return None
|
||||
|
||||
|
||||
def common_lisp_build(_root: Path, _spec: BenchmarkSpec, args: argparse.Namespace) -> tuple[list[str], list[str] | None]:
|
||||
if sbcl_command(args) is None:
|
||||
return [], ["missing SBCL; set SBCL or pass --sbcl"]
|
||||
return [], None
|
||||
|
||||
|
||||
def common_lisp_run(root: Path, spec: BenchmarkSpec, args: argparse.Namespace) -> list[str]:
|
||||
sbcl = sbcl_command(args)
|
||||
assert sbcl is not None
|
||||
return [
|
||||
sbcl,
|
||||
"--noinform",
|
||||
"--disable-debugger",
|
||||
"--script",
|
||||
str(root / "common-lisp" / f"{spec.source_stem}.lisp"),
|
||||
*spec.run_args,
|
||||
]
|
||||
|
||||
|
||||
def sbcl_command(args: argparse.Namespace) -> str | None:
|
||||
if args.sbcl:
|
||||
return args.sbcl
|
||||
env_path = os.environ.get("SBCL")
|
||||
if env_path:
|
||||
return env_path
|
||||
return shutil.which("sbcl")
|
||||
|
||||
|
||||
def build_dir(root: Path) -> Path:
|
||||
return root / "build"
|
||||
|
||||
|
||||
def positive_int(value: str) -> int:
|
||||
parsed = int(value)
|
||||
if parsed <= 0:
|
||||
raise argparse.ArgumentTypeError("value must be greater than zero")
|
||||
return parsed
|
||||
|
||||
|
||||
def non_negative_int(value: str) -> int:
|
||||
parsed = int(value)
|
||||
if parsed < 0:
|
||||
raise argparse.ArgumentTypeError("value must be zero or greater")
|
||||
return parsed
|
||||
|
||||
|
||||
def select_implementations(implementations: list[Implementation], names: list[str] | None) -> list[Implementation]:
|
||||
if not names:
|
||||
return implementations
|
||||
selected_names = set(names)
|
||||
return [impl for impl in implementations if impl.name in selected_names]
|
||||
|
||||
|
||||
def emit_list(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], as_json: bool) -> None:
|
||||
metadata = {
|
||||
"benchmark": spec.name,
|
||||
"loop_count": spec.loop_count,
|
||||
"hot_loop_count": spec.hot_loop_count,
|
||||
"expected_checksum": spec.expected_checksum,
|
||||
"hot_expected_checksum": spec.hot_expected_checksum,
|
||||
"timing_scope": TIMING_SCOPE,
|
||||
"timing_modes": TIMING_MODES,
|
||||
"loop_count_source": "stdin",
|
||||
"run_args": spec.run_args,
|
||||
"implementations": [
|
||||
{"name": impl.name, "language": impl.language, "source": str(impl.source.relative_to(root))}
|
||||
for impl in implementations
|
||||
],
|
||||
}
|
||||
|
||||
if as_json:
|
||||
print(json.dumps(metadata, indent=2, sort_keys=True))
|
||||
return
|
||||
|
||||
print(f"{spec.name}: {TIMING_SCOPE}")
|
||||
print(f"loop_count={spec.loop_count}")
|
||||
print(f"hot_loop_count={spec.hot_loop_count}")
|
||||
print("loop_count_source=stdin")
|
||||
if spec.run_args:
|
||||
print(f"run_args={' '.join(spec.run_args)}")
|
||||
print(f"expected_checksum={spec.expected_checksum}")
|
||||
print("implementations:")
|
||||
for impl in implementations:
|
||||
print(f" {impl.name}: {impl.language} ({impl.source.relative_to(root)})")
|
||||
|
||||
|
||||
def emit_dry_run(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], args: argparse.Namespace) -> None:
|
||||
params = run_parameters(spec, args.mode)
|
||||
print(f"{spec.name}: {TIMING_SCOPE}")
|
||||
print(f"mode={params.mode}")
|
||||
print(f"loop_count={params.loop_count}")
|
||||
print(f"expected_checksum={params.expected_checksum}")
|
||||
for impl in implementations:
|
||||
build_command, skip_reasons = impl.build(root, spec, args)
|
||||
print(f"{impl.name}:")
|
||||
if skip_reasons:
|
||||
print(f" skip: {'; '.join(skip_reasons)}")
|
||||
continue
|
||||
if build_command:
|
||||
print(f" build: {format_command(build_command)}")
|
||||
else:
|
||||
print(" build: none")
|
||||
print(f" stdin: {params.stdin_text.rstrip()}")
|
||||
print(f" run: {format_command(impl.run(root, spec, args))}")
|
||||
|
||||
|
||||
def run_benchmarks(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], args: argparse.Namespace) -> list[dict[str, object]]:
|
||||
build_dir(root).mkdir(exist_ok=True)
|
||||
return [run_one(root, spec, impl, args) for impl in implementations]
|
||||
|
||||
|
||||
def run_one(root: Path, spec: BenchmarkSpec, impl: Implementation, args: argparse.Namespace) -> dict[str, object]:
|
||||
params = run_parameters(spec, args.mode)
|
||||
build_command, skip_reasons = impl.build(root, spec, args)
|
||||
if skip_reasons:
|
||||
return skipped_result(impl, skip_reasons)
|
||||
|
||||
if build_command:
|
||||
build = run_command(build_command)
|
||||
if build.returncode != 0:
|
||||
return failed_result(impl, "build failed", build)
|
||||
|
||||
run_command_line = impl.run(root, spec, args)
|
||||
for _ in range(args.warmups):
|
||||
warmup = run_command(run_command_line, params.stdin_text)
|
||||
if not run_succeeded_for_params(warmup, params):
|
||||
return failed_result(impl, "warmup failed", warmup)
|
||||
|
||||
timings: list[int] = []
|
||||
for _ in range(args.repeats):
|
||||
start = time.perf_counter_ns()
|
||||
run = run_command(run_command_line, params.stdin_text)
|
||||
elapsed = time.perf_counter_ns() - start
|
||||
if not run_succeeded_for_params(run, params):
|
||||
return failed_result(impl, "run failed", run)
|
||||
timings.append(elapsed)
|
||||
|
||||
min_ms = ns_to_ms(min(timings))
|
||||
median_ms = ns_to_ms(int(statistics.median(timings)))
|
||||
max_ms = ns_to_ms(max(timings))
|
||||
normalization_factor = params.loop_count / params.base_loop_count
|
||||
return {
|
||||
"name": impl.name,
|
||||
"language": impl.language,
|
||||
"status": "ok",
|
||||
"timing_mode": params.mode,
|
||||
"loop_count": params.loop_count,
|
||||
"base_loop_count": params.base_loop_count,
|
||||
"normalization_factor": normalization_factor,
|
||||
"repeats": args.repeats,
|
||||
"warmups": args.warmups,
|
||||
"checksum": params.expected_checksum,
|
||||
"min_ms": min_ms,
|
||||
"median_ms": median_ms,
|
||||
"max_ms": max_ms,
|
||||
"normalized_min_ms": min_ms / normalization_factor,
|
||||
"normalized_median_ms": median_ms / normalization_factor,
|
||||
"normalized_max_ms": max_ms / normalization_factor,
|
||||
"timing_scope": TIMING_SCOPE,
|
||||
}
|
||||
|
||||
|
||||
def skipped_result(impl: Implementation, reasons: list[str]) -> dict[str, object]:
|
||||
return {"name": impl.name, "language": impl.language, "status": "skipped", "reason": "; ".join(reasons)}
|
||||
|
||||
|
||||
def failed_result(impl: Implementation, message: str, process: subprocess.CompletedProcess[str]) -> dict[str, object]:
|
||||
return {
|
||||
"name": impl.name,
|
||||
"language": impl.language,
|
||||
"status": "failed",
|
||||
"reason": message,
|
||||
"returncode": process.returncode,
|
||||
"stdout": process.stdout,
|
||||
"stderr": process.stderr,
|
||||
}
|
||||
|
||||
|
||||
def emit_results(spec: BenchmarkSpec, results: list[dict[str, object]], as_json: bool) -> None:
|
||||
if as_json:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"benchmark": spec.name,
|
||||
"base_loop_count": spec.loop_count,
|
||||
"timing_scope": TIMING_SCOPE,
|
||||
"results": results,
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
mode = next((str(result["timing_mode"]) for result in results if result["status"] == "ok"), "unknown")
|
||||
if mode == "hot-loop":
|
||||
loop_count = next((int(result["loop_count"]) for result in results if result["status"] == "ok"), spec.hot_loop_count)
|
||||
print(
|
||||
f"{spec.name}: {TIMING_SCOPE} "
|
||||
f"(mode=hot-loop, loop_count={loop_count}, normalized_to={spec.loop_count})"
|
||||
)
|
||||
else:
|
||||
print(f"{spec.name}: {TIMING_SCOPE}")
|
||||
for result in results:
|
||||
status = result["status"]
|
||||
name = result["name"]
|
||||
if status == "ok":
|
||||
if result["timing_mode"] == "hot-loop":
|
||||
print(
|
||||
"{name}: total_min={min_ms:.3f}ms total_median={median_ms:.3f}ms "
|
||||
"total_max={max_ms:.3f}ms normalized_median={normalized_median_ms:.3f}ms".format(
|
||||
name=name,
|
||||
min_ms=result["min_ms"],
|
||||
median_ms=result["median_ms"],
|
||||
max_ms=result["max_ms"],
|
||||
normalized_median_ms=result["normalized_median_ms"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"{name}: min={min_ms:.3f}ms median={median_ms:.3f}ms max={max_ms:.3f}ms".format(
|
||||
name=name,
|
||||
min_ms=result["min_ms"],
|
||||
median_ms=result["median_ms"],
|
||||
max_ms=result["max_ms"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(f"{name}: {status} ({result['reason']})")
|
||||
|
||||
|
||||
def run_command(command: list[str], stdin_text: str | None = None) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
command,
|
||||
input=stdin_text,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def normalized_stdout(stdout: str) -> str:
|
||||
lines = [line.strip() for line in stdout.splitlines() if line.strip()]
|
||||
if not lines:
|
||||
return ""
|
||||
return lines[-1]
|
||||
|
||||
|
||||
def run_parameters(spec: BenchmarkSpec, mode: str) -> RunParameters:
|
||||
if mode == "hot-loop":
|
||||
return RunParameters(
|
||||
mode=mode,
|
||||
loop_count=spec.hot_loop_count,
|
||||
expected_checksum=spec.hot_expected_checksum,
|
||||
stdin_text=spec.hot_stdin_text,
|
||||
base_loop_count=spec.loop_count,
|
||||
)
|
||||
return RunParameters(
|
||||
mode="cold-process",
|
||||
loop_count=spec.loop_count,
|
||||
expected_checksum=spec.expected_checksum,
|
||||
stdin_text=spec.stdin_text,
|
||||
base_loop_count=spec.loop_count,
|
||||
)
|
||||
|
||||
|
||||
def run_succeeded_for_params(process: subprocess.CompletedProcess[str], params: RunParameters) -> bool:
|
||||
if normalized_stdout(process.stdout) != params.expected_checksum:
|
||||
return False
|
||||
if params.mode == "hot-loop":
|
||||
return True
|
||||
return process.returncode == 0
|
||||
|
||||
|
||||
def ns_to_ms(value: int) -> float:
|
||||
return value / 1_000_000.0
|
||||
|
||||
|
||||
def first_available(candidates: list[str]) -> str | None:
|
||||
for candidate in candidates:
|
||||
found = shutil.which(candidate)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def format_command(command: list[str]) -> str:
|
||||
return " ".join(shlex_quote(part) for part in command)
|
||||
|
||||
|
||||
def shlex_quote(value: str) -> str:
|
||||
if value and all(char.isalnum() or char in "/._:-" for char in value):
|
||||
return value
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
1
benchmarks/string-eq-loop/.gitignore
vendored
Normal file
1
benchmarks/string-eq-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
71
benchmarks/string-eq-loop/README.md
Normal file
71
benchmarks/string-eq-loop/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# String Equality Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-119`.
|
||||
|
||||
This benchmark compares exact ASCII string content equality plus `i32`
|
||||
checksum accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/string_eq_loop.c`: C comparison implementation
|
||||
- `rust/string_eq_loop.rs`: Rust comparison implementation
|
||||
- `python/string_eq_loop.py`: Python comparison implementation
|
||||
- `clojure/string_eq_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/string_eq_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `4600001` for loop count `1000000` and
|
||||
target string `omega`. Hot-loop mode uses loop count `10000000` and checksum
|
||||
`46000001`. The runner supplies the loop count and target string at runtime.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/string-eq-loop/run.py --list
|
||||
python3 benchmarks/string-eq-loop/run.py --dry-run
|
||||
python3 benchmarks/string-eq-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/string-eq-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/string-eq-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/string-eq-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop keeps one fixed immutable string array in local scope,
|
||||
indexes it with a loop-carried `% 5` position, and compares the selected
|
||||
string against a runtime-supplied ASCII target by content equality only.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
11
benchmarks/string-eq-loop/benchmark.json
Normal file
11
benchmarks/string-eq-loop/benchmark.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"benchmark": "string-eq-loop",
|
||||
"source_stem": "string_eq_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "4600001",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "46000001",
|
||||
"hot_stdin": "10000000\n",
|
||||
"run_args": ["omega"]
|
||||
}
|
||||
39
benchmarks/string-eq-loop/c/string_eq_loop.c
Normal file
39
benchmarks/string-eq-loop/c/string_eq_loop.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 4600001
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static const char *configured_target(int argc, char **argv) {
|
||||
return argc > 1 ? argv[1] : "omega";
|
||||
}
|
||||
|
||||
static int32_t string_eq_loop(int32_t limit, const char *target) {
|
||||
static const char *words[5] = {"alpha", "omega", "delta", "omega", "sigma"};
|
||||
int32_t i = 0;
|
||||
int32_t acc = 1;
|
||||
|
||||
while (i < limit) {
|
||||
const char *current = words[i % 5];
|
||||
acc = strcmp(current, target) == 0 ? acc + 7 : acc + 3;
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int32_t result = string_eq_loop(configured_loop_count(), configured_target(argc, argv));
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
35
benchmarks/string-eq-loop/clojure/string_eq_loop.clj
Normal file
35
benchmarks/string-eq-loop/clojure/string_eq_loop.clj
Normal file
@ -0,0 +1,35 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 4600001)
|
||||
(def words ["alpha" "omega" "delta" "omega" "sigma"])
|
||||
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn configured-target []
|
||||
(or (first *command-line-args*) "omega"))
|
||||
|
||||
(defn string-eq-loop [limit target]
|
||||
(loop [i 0
|
||||
acc 1]
|
||||
(if (< i limit)
|
||||
(let [current (nth words (rem i 5))
|
||||
next (if (= ^String current ^String target)
|
||||
(+ acc 7)
|
||||
(+ acc 3))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc)))
|
||||
|
||||
(let [result (string-eq-loop (configured-loop-count) (configured-target))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
43
benchmarks/string-eq-loop/common-lisp/string_eq_loop.lisp
Normal file
43
benchmarks/string-eq-loop/common-lisp/string_eq_loop.lisp
Normal file
@ -0,0 +1,43 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 4600001)
|
||||
(defparameter +words+ #("alpha" "omega" "delta" "omega" "sigma"))
|
||||
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function () string) configured-target))
|
||||
(defun configured-target ()
|
||||
(or (second sb-ext:*posix-argv*) "omega"))
|
||||
|
||||
(declaim (ftype (function (fixnum string) fixnum) string-eq-loop))
|
||||
(defun string-eq-loop (limit target)
|
||||
(declare (type fixnum limit)
|
||||
(type string target))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 1
|
||||
while (< i limit)
|
||||
do (let* ((current (svref +words+ (rem i 5)))
|
||||
(next (if (string= current target)
|
||||
(+ acc 7)
|
||||
(+ acc 3)))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type string current)
|
||||
(type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc)))
|
||||
|
||||
(let ((result (string-eq-loop (configured-loop-count) (configured-target))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
42
benchmarks/string-eq-loop/python/string_eq_loop.py
Normal file
42
benchmarks/string-eq-loop/python/string_eq_loop.py
Normal file
@ -0,0 +1,42 @@
|
||||
import sys
|
||||
|
||||
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 4_600_001
|
||||
WORDS = ["alpha", "omega", "delta", "omega", "sigma"]
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def configured_target() -> str:
|
||||
return sys.argv[1] if len(sys.argv) > 1 else "omega"
|
||||
|
||||
|
||||
def string_eq_loop(limit: int, target: str) -> int:
|
||||
i = 0
|
||||
acc = 1
|
||||
|
||||
while i < limit:
|
||||
current = WORDS[i % 5]
|
||||
acc = acc + 7 if current == target else acc + 3
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = string_eq_loop(configured_loop_count(), configured_target())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/string-eq-loop/run.py
Normal file
13
benchmarks/string-eq-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local string-eq-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
48
benchmarks/string-eq-loop/rust/string_eq_loop.rs
Normal file
48
benchmarks/string-eq-loop/rust/string_eq_loop.rs
Normal file
@ -0,0 +1,48 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 4_600_001;
|
||||
const WORDS: [&str; 5] = ["alpha", "omega", "delta", "omega", "sigma"];
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn configured_target() -> String {
|
||||
std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "omega".to_string())
|
||||
}
|
||||
|
||||
fn string_eq_loop(limit: i32, target: &str) -> i32 {
|
||||
let mut i = 0;
|
||||
let mut acc = 1;
|
||||
|
||||
while i < limit {
|
||||
let current = WORDS[(i % 5) as usize];
|
||||
acc = if current == target { acc + 7 } else { acc + 3 };
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let target = configured_target();
|
||||
let result = string_eq_loop(configured_loop_count(), &target);
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/string-eq-loop/slovo.toml
Normal file
4
benchmarks/string-eq-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "string-eq-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
65
benchmarks/string-eq-loop/src/main.slo
Normal file
65
benchmarks/string-eq-loop/src/main.slo
Normal file
@ -0,0 +1,65 @@
|
||||
; Benchmark scaffold fixture for local-machine string-equality timing comparisons only.
|
||||
; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures.
|
||||
; The runner supplies the target through argv and the loop count through stdin
|
||||
; or argv so the equality path stays runtime-configured.
|
||||
|
||||
(module main)
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
4600001)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 2)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn target_text () -> string
|
||||
(std.process.arg 1))
|
||||
|
||||
(fn words () -> (array string 5)
|
||||
(array string "alpha" "omega" "delta" "omega" "sigma"))
|
||||
|
||||
(fn string_eq_loop ((limit i32) (target string)) -> i32
|
||||
(let values (array string 5) (words))
|
||||
(var i i32 0)
|
||||
(var acc i32 1)
|
||||
(while (< i limit)
|
||||
(set acc (if (= (index values (% i 5)) target)
|
||||
(+ acc 7)
|
||||
(+ acc 3)))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (string_eq_loop (configured_loop_count) (target_text)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "string equality loop checksum is deterministic"
|
||||
(= (string_eq_loop (loop_count) "omega") (expected_checksum)))
|
||||
1
benchmarks/vec-i32-index-loop/.gitignore
vendored
Normal file
1
benchmarks/vec-i32-index-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
73
benchmarks/vec-i32-index-loop/README.md
Normal file
73
benchmarks/vec-i32-index-loop/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Vec I32 Index Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-123`.
|
||||
|
||||
This benchmark compares runtime-owned `(vec i32)` indexing plus scalar
|
||||
accumulation across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on
|
||||
the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/vec_i32_index_loop.c`: C comparison implementation
|
||||
- `rust/vec_i32_index_loop.rs`: Rust comparison implementation
|
||||
- `python/vec_i32_index_loop.py`: Python comparison implementation
|
||||
- `clojure/vec_i32_index_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/vec_i32_index_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `3875007` for loop count `1000000`.
|
||||
Hot-loop mode uses loop count `10000000` and checksum `38750007`. The runner
|
||||
supplies the loop count at runtime so native compilers cannot fold the loop
|
||||
into a constant answer.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --list
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --dry-run
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/vec-i32-index-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop keeps one immutable runtime-owned `i32` vector in local
|
||||
scope, indexes it with a loop-carried `% 8` position, and accumulates the
|
||||
selected value into an `i32` checksum.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
10
benchmarks/vec-i32-index-loop/benchmark.json
Normal file
10
benchmarks/vec-i32-index-loop/benchmark.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"benchmark": "vec-i32-index-loop",
|
||||
"source_stem": "vec_i32_index_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "3875007",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "38750007",
|
||||
"hot_stdin": "10000000\n"
|
||||
}
|
||||
60
benchmarks/vec-i32-index-loop/c/vec_i32_index_loop.c
Normal file
60
benchmarks/vec-i32-index-loop/c/vec_i32_index_loop.c
Normal file
@ -0,0 +1,60 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LOOP_COUNT 1000000
|
||||
#define EXPECTED_CHECKSUM 3875007
|
||||
|
||||
typedef struct {
|
||||
int32_t len;
|
||||
int32_t *data;
|
||||
} vec_i32;
|
||||
|
||||
static int32_t configured_loop_count(void) {
|
||||
int32_t value = LOOP_COUNT;
|
||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static vec_i32 make_digits(void) {
|
||||
vec_i32 values;
|
||||
values.len = 8;
|
||||
values.data = malloc(sizeof(int32_t) * (size_t)values.len);
|
||||
if (values.data == NULL) {
|
||||
fputs("allocation failed\n", stderr);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
values.data[0] = 3;
|
||||
values.data[1] = 1;
|
||||
values.data[2] = 4;
|
||||
values.data[3] = 1;
|
||||
values.data[4] = 5;
|
||||
values.data[5] = 9;
|
||||
values.data[6] = 2;
|
||||
values.data[7] = 6;
|
||||
return values;
|
||||
}
|
||||
|
||||
static int32_t vec_i32_index_loop(int32_t limit) {
|
||||
vec_i32 digits = make_digits();
|
||||
int32_t i = 0;
|
||||
int32_t acc = 7;
|
||||
|
||||
while (i < limit) {
|
||||
acc = acc + digits.data[i % digits.len];
|
||||
acc = acc > 1000000000 ? acc - 1000000000 : acc;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
free(digits.data);
|
||||
return acc;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int32_t result = vec_i32_index_loop(configured_loop_count());
|
||||
printf("%d\n", result);
|
||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
||||
}
|
||||
28
benchmarks/vec-i32-index-loop/clojure/vec_i32_index_loop.clj
Normal file
28
benchmarks/vec-i32-index-loop/clojure/vec_i32_index_loop.clj
Normal file
@ -0,0 +1,28 @@
|
||||
(set! *warn-on-reflection* true)
|
||||
(set! *unchecked-math* :warn-on-boxed)
|
||||
|
||||
(def loop-count 1000000)
|
||||
(def expected-checksum 3875007)
|
||||
(defn configured-loop-count []
|
||||
(try
|
||||
(let [line (read-line)
|
||||
value (Integer/parseInt (.trim ^String line))]
|
||||
(if (pos? value) value loop-count))
|
||||
(catch Exception _
|
||||
loop-count)))
|
||||
|
||||
(defn vec-i32-index-loop [limit]
|
||||
(let [digits [3 1 4 1 5 9 2 6]]
|
||||
(loop [i 0
|
||||
acc 7]
|
||||
(if (< i limit)
|
||||
(let [next (+ acc (nth digits (rem i 8)))
|
||||
bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)]
|
||||
(recur (inc i) bounded))
|
||||
acc))))
|
||||
|
||||
(let [result (vec-i32-index-loop (configured-loop-count))]
|
||||
(println result)
|
||||
(System/exit (if (= result expected-checksum) 0 1)))
|
||||
@ -0,0 +1,33 @@
|
||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
||||
|
||||
(defconstant +loop-count+ 1000000)
|
||||
(defconstant +expected-checksum+ 3875007)
|
||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
||||
(defun configured-loop-count ()
|
||||
(handler-case
|
||||
(let ((line (read-line *standard-input* nil nil)))
|
||||
(if line
|
||||
(let ((value (parse-integer line :junk-allowed t)))
|
||||
(if (> value 0) value +loop-count+))
|
||||
+loop-count+))
|
||||
(error () +loop-count+)))
|
||||
|
||||
(declaim (ftype (function (fixnum) fixnum) vec-i32-index-loop))
|
||||
(defun vec-i32-index-loop (limit)
|
||||
(declare (type fixnum limit))
|
||||
(let ((digits #(3 1 4 1 5 9 2 6)))
|
||||
(loop with i of-type fixnum = 0
|
||||
with acc of-type fixnum = 7
|
||||
while (< i limit)
|
||||
do (let* ((next (+ acc (svref digits (rem i 8))))
|
||||
(bounded (if (> next 1000000000)
|
||||
(- next 1000000000)
|
||||
next)))
|
||||
(declare (type fixnum next bounded))
|
||||
(setf acc bounded
|
||||
i (+ i 1)))
|
||||
finally (return acc))))
|
||||
|
||||
(let ((result (vec-i32-index-loop (configured-loop-count))))
|
||||
(format t "~D~%" result)
|
||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
||||
34
benchmarks/vec-i32-index-loop/python/vec_i32_index_loop.py
Normal file
34
benchmarks/vec-i32-index-loop/python/vec_i32_index_loop.py
Normal file
@ -0,0 +1,34 @@
|
||||
LOOP_COUNT = 1_000_000
|
||||
EXPECTED_CHECKSUM = 3_875_007
|
||||
|
||||
|
||||
def configured_loop_count() -> int:
|
||||
try:
|
||||
value = int(input().strip())
|
||||
except (EOFError, ValueError):
|
||||
return LOOP_COUNT
|
||||
|
||||
return value if value > 0 else LOOP_COUNT
|
||||
|
||||
|
||||
def vec_i32_index_loop(limit: int) -> int:
|
||||
digits = [3, 1, 4, 1, 5, 9, 2, 6]
|
||||
i = 0
|
||||
acc = 7
|
||||
|
||||
while i < limit:
|
||||
acc += digits[i % len(digits)]
|
||||
acc = acc - 1_000_000_000 if acc > 1_000_000_000 else acc
|
||||
i += 1
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def main() -> int:
|
||||
result = vec_i32_index_loop(configured_loop_count())
|
||||
print(result)
|
||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
benchmarks/vec-i32-index-loop/run.py
Normal file
13
benchmarks/vec-i32-index-loop/run.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local vec-i32-index-loop benchmark scaffold."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from runner import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
||||
40
benchmarks/vec-i32-index-loop/rust/vec_i32_index_loop.rs
Normal file
40
benchmarks/vec-i32-index-loop/rust/vec_i32_index_loop.rs
Normal file
@ -0,0 +1,40 @@
|
||||
const LOOP_COUNT: i32 = 1_000_000;
|
||||
const EXPECTED_CHECKSUM: i32 = 3_875_007;
|
||||
|
||||
fn configured_loop_count() -> i32 {
|
||||
let mut input = String::new();
|
||||
if std::io::stdin().read_line(&mut input).is_err() {
|
||||
return LOOP_COUNT;
|
||||
}
|
||||
|
||||
input
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(LOOP_COUNT)
|
||||
}
|
||||
|
||||
fn vec_i32_index_loop(limit: i32) -> i32 {
|
||||
let digits = vec![3, 1, 4, 1, 5, 9, 2, 6];
|
||||
let mut i = 0;
|
||||
let mut acc = 7;
|
||||
|
||||
while i < limit {
|
||||
acc += digits[(i % digits.len() as i32) as usize];
|
||||
acc = if acc > 1_000_000_000 {
|
||||
acc - 1_000_000_000
|
||||
} else {
|
||||
acc
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = vec_i32_index_loop(configured_loop_count());
|
||||
println!("{}", result);
|
||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
||||
}
|
||||
4
benchmarks/vec-i32-index-loop/slovo.toml
Normal file
4
benchmarks/vec-i32-index-loop/slovo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "vec-i32-index-loop"
|
||||
source_root = "src"
|
||||
entry = "main"
|
||||
62
benchmarks/vec-i32-index-loop/src/main.slo
Normal file
62
benchmarks/vec-i32-index-loop/src/main.slo
Normal file
@ -0,0 +1,62 @@
|
||||
; Benchmark scaffold fixture for local-machine vec-i32-index timing comparisons only.
|
||||
; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures.
|
||||
; The runner supplies the loop count through stdin or argv so native compilers
|
||||
; cannot fold the benchmark loop into a constant answer.
|
||||
|
||||
(module main)
|
||||
|
||||
(import std.vec_i32 (empty append2 append3 at))
|
||||
|
||||
(fn loop_count () -> i32
|
||||
1000000)
|
||||
|
||||
(fn expected_checksum () -> i32
|
||||
3875007)
|
||||
|
||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
||||
(let input (result string i32) (std.io.read_stdin_result))
|
||||
(match input
|
||||
((ok text)
|
||||
(std.string.parse_i32_result text))
|
||||
((err code)
|
||||
(err i32 i32 code))))
|
||||
|
||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
||||
(std.string.parse_i32_result (std.process.arg 1)))
|
||||
|
||||
(fn configured_stdin_loop_count () -> i32
|
||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
||||
(if (is_ok parsed_stdin)
|
||||
(unwrap_ok parsed_stdin)
|
||||
(loop_count)))
|
||||
|
||||
(fn configured_loop_count () -> i32
|
||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
||||
(if (is_ok parsed_arg)
|
||||
(unwrap_ok parsed_arg)
|
||||
(configured_stdin_loop_count)))
|
||||
|
||||
(fn values () -> (vec i32)
|
||||
(append2 (append3 (append3 (empty) 3 1 4) 1 5 9) 2 6))
|
||||
|
||||
(fn vec_i32_index_loop ((limit i32)) -> i32
|
||||
(let digits (vec i32) (values))
|
||||
(var i i32 0)
|
||||
(var acc i32 7)
|
||||
(while (< i limit)
|
||||
(set acc (+ acc (at digits (% i 8))))
|
||||
(set acc (if (> acc 1000000000)
|
||||
(- acc 1000000000)
|
||||
acc))
|
||||
(set i (+ i 1)))
|
||||
acc)
|
||||
|
||||
(fn main () -> i32
|
||||
(let result i32 (vec_i32_index_loop (configured_loop_count)))
|
||||
(std.io.print_i32 result)
|
||||
(if (= result (expected_checksum))
|
||||
0
|
||||
1))
|
||||
|
||||
(test "vec i32 index loop checksum is deterministic"
|
||||
(= (vec_i32_index_loop (loop_count)) (expected_checksum)))
|
||||
1
benchmarks/vec-string-eq-loop/.gitignore
vendored
Normal file
1
benchmarks/vec-string-eq-loop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
||||
73
benchmarks/vec-string-eq-loop/README.md
Normal file
73
benchmarks/vec-string-eq-loop/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Vec String Equality Loop Benchmark Scaffold
|
||||
|
||||
Release: `exp-123`.
|
||||
|
||||
This benchmark compares runtime-owned `(vec string)` indexing plus exact ASCII
|
||||
string content equality and `i32` checksum accumulation across Slovo, C, Rust,
|
||||
Python, Clojure, and Common Lisp/SBCL on the same machine.
|
||||
|
||||
It is not a published benchmark result, performance threshold, optimizer
|
||||
claim, or cross-machine comparison.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main.slo`: Slovo project benchmark fixture
|
||||
- `c/vec_string_eq_loop.c`: C comparison implementation
|
||||
- `rust/vec_string_eq_loop.rs`: Rust comparison implementation
|
||||
- `python/vec_string_eq_loop.py`: Python comparison implementation
|
||||
- `clojure/vec_string_eq_loop.clj`: Clojure comparison implementation
|
||||
- `common-lisp/vec_string_eq_loop.lisp`: Common Lisp/SBCL comparison implementation
|
||||
- `run.py`: build/run/timing harness
|
||||
|
||||
All implementations print checksum `4600001` for loop count `1000000` and
|
||||
target string `omega`. Hot-loop mode uses loop count `10000000` and checksum
|
||||
`46000001`. The runner supplies the loop count and target string at runtime.
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the Glagol repository root:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --list
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --dry-run
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --only python --repeats 3 --warmups 1
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
||||
```
|
||||
|
||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
||||
available:
|
||||
|
||||
```bash
|
||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --glagol compiler/target/debug/glagol
|
||||
```
|
||||
|
||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
||||
Use `--only` multiple times to select implementations:
|
||||
|
||||
```bash
|
||||
python3 benchmarks/vec-string-eq-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
||||
```
|
||||
|
||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
||||
|
||||
## Comparison Method
|
||||
|
||||
- The runner builds each implementation once before timing. The reported
|
||||
numbers measure execution only, not compile time.
|
||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
||||
- C timings use `clang -O2 -std=c11`.
|
||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
||||
- The measured loop keeps one immutable runtime-owned string vector in local
|
||||
scope, indexes it with a loop-carried `% 5` position, and compares the
|
||||
selected string against a runtime-supplied ASCII target by content equality
|
||||
only.
|
||||
|
||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
||||
startup.
|
||||
|
||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
||||
and reports total time plus normalized time for the base `1000000` loop count.
|
||||
11
benchmarks/vec-string-eq-loop/benchmark.json
Normal file
11
benchmarks/vec-string-eq-loop/benchmark.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"benchmark": "vec-string-eq-loop",
|
||||
"source_stem": "vec_string_eq_loop",
|
||||
"loop_count": 1000000,
|
||||
"expected_checksum": "4600001",
|
||||
"stdin": "1000000\n",
|
||||
"hot_loop_count": 10000000,
|
||||
"hot_expected_checksum": "46000001",
|
||||
"hot_stdin": "10000000\n",
|
||||
"run_args": ["omega"]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user