sunchao commented on code in PR #11279:
URL: https://github.com/apache/arrow-rs/pull/11279#discussion_r4170834321
##########
arrow-select/src/interleave.rs:
##########
@@ -125,6 +128,89 @@ pub fn interleave(
}
}
+/// Interleaves byte view arrays into independently owned, compact buffers.
Review Comment:
I switched this to
`Interleaver::new().with_compact_byte_views(true).interleave(...)` and removed
the additional public free function. An optional
`with_preserve_byte_view_sharing(true)` copies a repeated source byte range
once into the output; it is separate because tracking ranges has a cost. These
options apply to top-level StringView/BinaryView arrays. The configurable
record-batch method applies the same options column by column.
On current head `c8ee97880e46af9257a41e8df355bd9bcf23f238`, the original
free `interleave` function body matches exact base
`705e6405b56cc01efb1c267203d240d0d85804ab` and has no executable reference to
compact kernels. The configurable method delegates to it for defaults and other
types. Disassembly while investigating the controls showed two additional
outlined setup calls. After restoring local validation and adding an inlining
hint to `Interleave::new`, those calls are absent in the measured build. That
is not a general compiler or zero-overhead guarantee.
One integer-only client calling the original free API grew from 2,741,056 to
2,742,800 stripped bytes (+1,744, 0.064%) on exact base versus this head: Rust
1.99.0, optimization level 3, one client codegen unit, LTO off, matching
dependency sources/features. Both clients checked identical output. This is a
narrow example, not a guarantee for other clients or link settings.
The description and reproduction comments contain fresh
exact-base/current-head timing and allocation evidence, including adverse
cases. I kept the PR in draft while we settle the API; this thread remains open
for design acceptance.
<details>
<summary>Reproduce the narrow original-free-API code-size
comparison</summary>
This is one nullable-Int32 caller of the original free `interleave` API,
not a general application-size or runtime claim. The matched final run
measured
2,741,056 stripped bytes at base and 2,742,800 at head (+1,744; +0.064%).
Absolute paths, target platform, and linker layout can affect exact sizes.
After the timing reproduction has built the exact revisions, run the
following
from the directory containing `arrow-base/`, `arrow-head/`,
`timing-results/build-base.jsonl`, and `timing-results/build-head.jsonl`.
These must be the local Cargo
JSON build records for those checkouts, not logs copied from another machine.
This only links two small clients against the existing release dependencies.
```bash
set -eu
mkdir -p code-size-repro
cat > code-size-repro/client.rs <<'RS'
use std::hint::black_box;
use arrow_array::{Array, Int32Array};
use arrow_select::interleave::interleave;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let left = Int32Array::from(vec![Some(10), None, Some(30), Some(40)]);
let right = Int32Array::from(vec![-7, -8, -9]);
let arrays: [&dyn Array; 2] = [&left, &right];
let indices = [(1, 0), (0, 2), (0, 1), (1, 2), (0, 0), (0, 2)];
let result = interleave(black_box(&arrays), black_box(&indices))?;
let result = result.as_any().downcast_ref::<Int32Array>().unwrap();
let actual: Vec<Option<i32>> = result.iter().collect();
assert_eq!(actual, [Some(-7), Some(30), None, Some(-9), Some(10),
Some(30)]);
println!("{:?}", black_box(actual));
Ok(())
}
RS
python3 - <<'PY'
import hashlib
import json
import pathlib
import shlex
import subprocess
root = pathlib.Path.cwd()
out = root / "code-size-repro"
base = root / "arrow-base"
head = root / "arrow-head"
kernel = "705e6405b56cc01efb1c267203d240d0d85804ab"
base_sha = "0b317f8a56949f4872bfdf4826e779ce5ff9aaf1"
head_sha = "c8ee97880e46af9257a41e8df355bd9bcf23f238"
def check(condition, message):
if not condition:
raise RuntimeError(message)
def git(checkout, *args):
return subprocess.check_output(
["git", "-C", str(checkout), *args], text=True
).strip()
def digest(path):
value = hashlib.sha256(path.read_bytes()).hexdigest()
print(f"sha256 {value} {path}")
return value
for checkout, revision in [(base, base_sha), (head, head_sha)]:
check(git(checkout, "rev-parse", "HEAD") == revision, "Wrong revision")
check(not git(checkout, "status", "--porcelain"), "Dirty checkout")
check(git(checkout, "merge-base", kernel, revision) == kernel,
"Wrong kernel ancestry")
# The companion changes benchmarks only; the PR also changes
interleave.rs.
excludes = [
":!arrow/benches/interleave_kernels.rs",
":!arrow/benches/interleave_byte_view_cases/mod.rs",
]
if checkout == head:
excludes.append(":!arrow-select/src/interleave.rs")
git(checkout, "diff", "--exit-code", kernel, revision, "--", ".",
*excludes)
print(f"source {checkout.name} {revision} tree={git(checkout,
'rev-parse', 'HEAD^{tree}')}")
check((base / "Cargo.lock").read_bytes() == (head /
"Cargo.lock").read_bytes(),
"Cargo locks differ")
client = out / "client.rs"
check(digest(client) ==
"a305041224c2cd1bbe5f78dceef0833ed7a93869f0ab9fb4b5ce1f44fabc47d1",
"Client source differs")
digest(base / "Cargo.lock")
version = subprocess.check_output(["rustc", "+1.99.0", "-V"],
text=True).strip()
check(version == "rustc 1.99.0 (b940084d7 2026-09-28)", "Wrong Rust
compiler")
subprocess.run(["rustc", "+1.99.0", "-Vv"], check=True)
def artifacts(checkout, log):
# Select actual compiler artifacts by target and source manifest, not
globs.
records = [json.loads(line) for line in log.read_text().splitlines() if
line]
libs, fingerprints = {}, {}
profile = {"opt_level": "3", "debuginfo": 0, "debug_assertions": False,
"overflow_checks": False, "test": False}
for target, package in [("arrow_array", "arrow-array"),
("arrow_select", "arrow-select")]:
manifest = (checkout / package / "Cargo.toml").resolve()
matching = [r for r in records
if r.get("reason") == "compiler-artifact"
and r["target"]["name"] == target
and pathlib.Path(r["manifest_path"]).resolve() ==
manifest]
check(bool(matching), f"Missing {target} artifact in {log}")
for record in matching:
check(record["features"] == [] and record["profile"] == profile,
f"Unexpected release profile/features for {target}")
paths = {pathlib.Path(name).resolve() for r in matching
for name in r["filenames"] if name.endswith(".rlib")}
check(len(paths) == 1, f"Ambiguous {target} rlib")
lib = paths.pop()
deps = (checkout / "target/release/deps").resolve()
check(lib.is_file() and lib.parent == deps, "Wrong dependency
directory")
libs[target] = lib
# Derive the fingerprint identity from the selected artifact.
suffix = lib.stem.rsplit("-", 1)[1]
fingerprint = deps.parent / ".fingerprint" / f"{package}-{suffix}"
fp = json.loads((fingerprint / f"lib-{target}.json").read_text())
check(fp["features"] == "[]" and fp["rustflags"] == []
and fp["compile_kind"] == 0, "Unexpected dependency build
flags")
fingerprints[target] = fp
digest(lib)
digest(log)
return libs, fingerprints
base_libs, base_fp = artifacts(base, root /
"timing-results/build-base.jsonl")
head_libs, head_fp = artifacts(head, root /
"timing-results/build-head.jsonl")
check(base_fp == head_fp, "Dependency fingerprints/profile inputs differ")
outputs, binaries = [], []
for label, checkout, libs in [("base", base, base_libs), ("head", head,
head_libs)]:
binary = out / f"{label}-client"
command = [
"rustc", "+1.99.0", "--edition=2024", "--crate-name",
"interleave_client",
"-C", "opt-level=3", "-C", "debuginfo=0", "-C", "strip=symbols",
"-C", "codegen-units=1", "-C", "lto=off",
"--extern", f"arrow_array={libs['arrow_array']}",
"--extern", f"arrow_select={libs['arrow_select']}",
"-L", f"dependency={libs['arrow_select'].parent}",
str(client), "-o", str(binary),
]
print(shlex.join(command), flush=True)
subprocess.run(command, check=True)
outputs.append(subprocess.check_output([str(binary)], text=True))
binaries.append(binary)
digest(binary)
expected = "[Some(-7), Some(30), None, Some(-9), Some(10), Some(30)]\n"
check(outputs == [expected, expected], "Client outputs differ or are
incorrect")
sizes = [binary.stat().st_size for binary in binaries]
print(expected, end="")
print(f"base={sizes[0]} head={sizes[1]} delta={sizes[1] - sizes[0]} bytes "
f"({100 * (sizes[1] - sizes[0]) / sizes[0]:+.3f}%)")
subprocess.run(["size", *map(str, binaries)], check=True)
PY
```
Each client uses its own checkout's complete release dependency graph; no
base/head rlibs are mixed. Matching fingerprints verify compiler/profile/flag
inputs, not byte identity of separately compiled libraries. The checksum and
command output supplies local provenance.
GNU `size`'s text total includes read-only sections, and `bss` is not on-disk
payload. This experiment does not measure RSS, allocation peaks, compact API
code size, or runtime performance.
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]