Skip to content

Commit

Permalink
libc: Use a byte slice for strftime format
Browse files Browse the repository at this point in the history
Converting the strftime format Rust string slice into a C-compatible
string takes CPU cycles that can be avoided by using a byte slice
instead. Once c"…" string literals are stable, use those.

Benchmark results on a Raspberry Pi Zero 2W running Ubuntu 24.04 (noble)
arm64:

```
$ sudo 3cpio -x /boot/initrd.img -C /var/tmp/initrd
$ ( cd /var/tmp/initrd && find . | LC_ALL=C sort | sudo cpio --reproducible --quiet -o -H newc ) > initrd.img
$ hyperfine -N --warmup 3 --runs 100 "target/release/3cpio -tv initrd.img" "3cpio-0.3.0+8 -tv initrd.img"
Benchmark 1: target/release/3cpio -tv initrd.img
  Time (mean ± σ):     113.2 ms ±   1.1 ms    [User: 50.6 ms, System: 61.3 ms]
  Range (min … max):   111.2 ms … 116.6 ms    100 runs

Benchmark 2: 3cpio-0.3.0+8 -tv initrd.img
  Time (mean ± σ):     115.1 ms ±   1.0 ms    [User: 52.6 ms, System: 61.2 ms]
  Range (min … max):   113.1 ms … 118.5 ms    100 runs

Summary
  target/release/3cpio -tv initrd.img ran
    1.02 ± 0.01 times faster than 3cpio-0.3.0+8 -tv initrd.img
```
  • Loading branch information
bdrung committed Aug 6, 2024
1 parent 7f9377b commit afa4d9f
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ fn format_time(timestamp: u32, now: i64) -> Result<String> {
// on the average.
let recent = now - i64::from(timestamp) <= 15778476;
if recent {
strftime_local("%b %e %H:%M", timestamp)
strftime_local(b"%b %e %H:%M\0", timestamp)
} else {
strftime_local("%b %e %Y", timestamp)
strftime_local(b"%b %e %Y\0", timestamp)
}
}

Expand Down
22 changes: 14 additions & 8 deletions src/libc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ffi::CString;
use std::ffi::{CStr, CString};
use std::io::{Error, ErrorKind, Result};

/// Get password file entry and return user name.
Expand Down Expand Up @@ -81,18 +81,24 @@ pub fn set_modified(path: &str, mtime: i64) -> Result<()> {
Ok(())
}

fn strftime(format: &str, tm: *mut libc::tm) -> Result<String> {
let f = CString::new(format)?;
// TODO: Use c"…" string literal for `format` once stable
fn strftime(format: &[u8], tm: *mut libc::tm) -> Result<String> {
let mut s = [0u8; 19];
let length =
unsafe { libc::strftime(s.as_mut_ptr() as *mut libc::c_char, s.len(), f.as_ptr(), tm) };
let length = unsafe {
libc::strftime(
s.as_mut_ptr() as *mut libc::c_char,
s.len(),
CStr::from_bytes_with_nul_unchecked(format).as_ptr(),
tm,
)
};
if length == 0 {
return Err(Error::new(ErrorKind::Other, "strftime returned 0"));
}
Ok(String::from_utf8_lossy(&s[..length]).to_string())
}

pub fn strftime_local(format: &str, timestamp: u32) -> Result<String> {
pub fn strftime_local(format: &[u8], timestamp: u32) -> Result<String> {
let mut tm = std::mem::MaybeUninit::<libc::tm>::uninit();
let result = unsafe { libc::localtime_r(&timestamp.into(), tm.as_mut_ptr()) };
if result.is_null() {
Expand Down Expand Up @@ -170,15 +176,15 @@ mod tests {

#[test]
fn test_strftime_local_year() {
let time = strftime_local("%b %e %Y", 2278410030).unwrap();
let time = strftime_local(b"%b %e %Y\0", 2278410030).unwrap();
assert_eq!(time, "Mar 14 2042");
}

#[test]
fn test_strftime_local_hour() {
std::env::set_var("TZ", "UTC");
unsafe { tzset() };
let time = strftime_local("%b %e %H:%M", 1720735264).unwrap();
let time = strftime_local(b"%b %e %H:%M\0", 1720735264).unwrap();
assert_eq!(time, "Jul 11 22:01");
}
}

0 comments on commit afa4d9f

Please sign in to comment.