Skip to content

Commit 3c3a8ed

Browse files
committed
first commit
0 parents  commit 3c3a8ed

File tree

6 files changed

+1432
-0
lines changed

6 files changed

+1432
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.zig-cache/
2+
zig-out/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (Expat)
2+
3+
Copyright (c) Zig contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# small-array-list
2+
3+
This library provides a `SmallArrayList` type (and some variants), which is
4+
optimized for memory efficient storage of arrays that generally contain few
5+
items. This works by sharing the memory internal storage space between either
6+
a fixed size array, or an externally allocated slice, and switching between
7+
the two as needed. See the [Small Capacity Limit](#small-capacity-limit) for
8+
details on this storage mechanism.
9+
10+
```zig
11+
var list: SmallArrayList(i32) = .empty;
12+
defer list.deinit(allocator);
13+
14+
list.append(allocator, 1);
15+
list.append(allocator, 2);
16+
list.append(allocator, 3);
17+
18+
std.debug.print("len={} capacity={}\n", .{ list.len, list.capacity });
19+
// On 64-bit systems, this will be: len=3 capacity=4
20+
21+
for (list.items()) |item| {
22+
std.debug.print("item={}\n", .{ item });
23+
}
24+
25+
if (!list.hasAllocation()) {
26+
std.debug.print("no allocations!\n", .{});
27+
}
28+
```
29+
30+
> [!IMPORTANT]
31+
> Instances keep much of the interface in common with `std.array_list.ArrayListUnmanaged`
32+
> with a few important differences:
33+
>
34+
> 1. To get the number of items in the list, use `list.len` instead of `list.items.len`.
35+
> 1. To get the items of in the list, use `list.items()` instead of `list.items`.
36+
37+
## Small Capacity Limit
38+
39+
The available small capacity limit is determined by the type stored and the
40+
native machine word size. Because a zig slice requires two machine words for
41+
storing it's `ptr` and `len`, this space can be used for direct item storage
42+
instead until the small capacity is exceeded. For any given type `T`, the
43+
small array list can store up to `2*@sizeOf(usize) / @sizeOf(T)` items before
44+
requiring any internal allocation.
45+
46+
For example on a 64-bit processor will print out `smallCapacity=4 sizeOf=24`:
47+
48+
```zig
49+
const List = SmallArrayList(i32);
50+
std.debug.print("smallCapacity={} sizeOf={}\n", .{List.smallCapacity, @sizeOf(List)});
51+
```
52+
53+
The over size of a `SmallArrayList` is generally three machine words. This is
54+
achieved using a few trade-offs:
55+
56+
1. The overlapped memory of the internal array and external slice is achieved
57+
using a union. By ensuring this union is indiscriminate, the array list can
58+
maximize storage efficiency. This union is then discriminated using the
59+
`capacity` field of the array list. Using `SmallArrayListSized`, you can set
60+
a small capacity limit that exceeds the default size. This will cause the
61+
overall small array list size to grow.
62+
63+
2. The `len` and `capacity` are each half of a `usize`. This does mean it will result
64+
in an `error.OutOfMemory` when trying to allocate a capacity greater than
65+
half the maximum of a `std.array_list.ArrayList`. However, this library is
66+
optimized for small array lists. If lists of such size are needed, the standard
67+
library should be used.
68+
69+
3. All `SmallArrayList` types are unmanaged, meaning they do not store the
70+
`std.mem.Allocator` internally, and each function that could possibly allocate
71+
or deallocate takes the allocator as a parameter. Note that the same allocator
72+
instance must be used for each call into any single small array list.
73+
74+
> [!IMPORTANT]
75+
> One important thing to keep in mind when using larger types is that there is
76+
> a minimum small capacity of 1, so if the size of `T` exceeds two machine words,
77+
> the overall size of the `SmallArrayList` will expand. This can still be
78+
> beneficial, but it is something you'll want to consider.
79+
80+
## Variants
81+
82+
The `SmallArrayList` uses the default alignment of `T` and a small capacity
83+
determined by how many items of `T` can be stored in two machine words. However,
84+
both of these values may be overridden.
85+
86+
If you want to change the alignment, you can use either `SmallArrayListAligned`
87+
or `SmallArrayListAlignedSized`, passing in the desired alignment.
88+
89+
Changing the small capacity can be done using either `SmallArrayListSized` or
90+
`SmallArrayListAlignedSized`. Using a small capacity larger than the default
91+
will increase the overall size of the `SmallArrayList`, but it allows for
92+
storing more items before allocating.
93+
94+
For example, on a 64-bit system:
95+
96+
```zig
97+
const std = @import("std");
98+
const expect = std.testing.expect;
99+
100+
test "sizes" {
101+
const a = testing.allocator;
102+
103+
const List1 = SmallArrayList(i32);
104+
const List2 = SmallArrayListSized(i32, 6);
105+
106+
// This holds true on a 64-bit system
107+
try testing.expect(@sizeOf(List1) == 24);
108+
try testing.expect(@sizeOf(List2) == 32);
109+
110+
var list1: List1 = .empty;
111+
var list2: List2 = .empty;
112+
113+
defer list1.deinit(a);
114+
defer list2.deinit(a); // don't strictly need to deinit list2
115+
116+
for (0..List2.smallCapacity) |i| {
117+
try list1.append(a, @intCast(i));
118+
try list2.append(a, @intCast(i));
119+
}
120+
121+
try testing.expect(list1.hasAllocation());
122+
try testing.expect(!list2.hasAllocation());
123+
}
124+
```

build.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const unit_tests = b.addTest(.{
5+
.root_source_file = b.path("src/root.zig"),
6+
});
7+
8+
const run_unit_tests = b.addRunArtifact(unit_tests);
9+
10+
const test_step = b.step("test", "Run unit tests");
11+
test_step.dependOn(&run_unit_tests.step);
12+
}

build.zig.zon

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.{
2+
// This is the default name used by packages depending on this one. For
3+
// example, when a user runs `zig fetch --save <url>`, this field is used
4+
// as the key in the `dependencies` table. Although the user can choose a
5+
// different name, most users will stick with this provided value.
6+
//
7+
// It is redundant to include "zig" in this name because it is already
8+
// within the Zig package namespace.
9+
.name = .small_array_list,
10+
11+
// This is a [Semantic Version](https://semver.org/).
12+
// In a future version of Zig it will be used for package deduplication.
13+
.version = "0.0.0",
14+
15+
// Together with name, this represents a globally unique package
16+
// identifier. This field is generated by the Zig toolchain when the
17+
// package is first created, and then *never changes*. This allows
18+
// unambiguous detection of one package being an updated version of
19+
// another.
20+
//
21+
// When forking a Zig project, this id should be regenerated (delete the
22+
// field and run `zig build`) if the upstream project is still maintained.
23+
// Otherwise, the fork is *hostile*, attempting to take control over the
24+
// original project's identity. Thus it is recommended to leave the comment
25+
// on the following line intact, so that it shows up in code reviews that
26+
// modify the field.
27+
.fingerprint = 0x378608764b63102b, // Changing this has security and trust implications.
28+
29+
// Tracks the earliest Zig version that the package considers to be a
30+
// supported use case.
31+
.minimum_zig_version = "0.14.1",
32+
33+
// This field is optional.
34+
// Each dependency must either provide a `url` and `hash`, or a `path`.
35+
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
36+
// Once all dependencies are fetched, `zig build` no longer requires
37+
// internet connectivity.
38+
.dependencies = .{
39+
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
40+
//.example = .{
41+
// // When updating this field to a new URL, be sure to delete the corresponding
42+
// // `hash`, otherwise you are communicating that you expect to find the old hash at
43+
// // the new URL. If the contents of a URL change this will result in a hash mismatch
44+
// // which will prevent zig from using it.
45+
// .url = "https://example.com/foo.tar.gz",
46+
//
47+
// // This is computed from the file contents of the directory of files that is
48+
// // obtained after fetching `url` and applying the inclusion rules given by
49+
// // `paths`.
50+
// //
51+
// // This field is the source of truth; packages do not come from a `url`; they
52+
// // come from a `hash`. `url` is just one of many possible mirrors for how to
53+
// // obtain a package matching this `hash`.
54+
// //
55+
// // Uses the [multihash](https://multiformats.io/multihash/) format.
56+
// .hash = "...",
57+
//
58+
// // When this is provided, the package is found in a directory relative to the
59+
// // build root. In this case the package's hash is irrelevant and therefore not
60+
// // computed. This field and `url` are mutually exclusive.
61+
// .path = "foo",
62+
//
63+
// // When this is set to `true`, a package is declared to be lazily
64+
// // fetched. This makes the dependency only get fetched if it is
65+
// // actually used.
66+
// .lazy = false,
67+
//},
68+
},
69+
70+
// Specifies the set of files and directories that are included in this package.
71+
// Only files and directories listed here are included in the `hash` that
72+
// is computed for this package. Only files listed here will remain on disk
73+
// when using the zig package manager. As a rule of thumb, one should list
74+
// files required for compilation plus any license(s).
75+
// Paths are relative to the build root. Use the empty string (`""`) to refer to
76+
// the build root itself.
77+
// A directory listed here means that all files within, recursively, are included.
78+
.paths = .{
79+
"build.zig",
80+
"build.zig.zon",
81+
"src",
82+
// For example...
83+
//"LICENSE",
84+
//"README.md",
85+
},
86+
}

0 commit comments

Comments
 (0)