9.9 - צמצום העתקות ובעלות על נתוני רשת - פתרון
פתרון מסלולי ההעברה¶
פתרון ראשון - בסיס השוואה¶
הקטע הבא מחליף את לולאת sendfile בתוכנית ההרצאה. יש להוסיף את ההבאה של FileExt ולהגדיר את הסוקט כמשתנה שניתן לשנות.
use std::os::unix::fs::FileExt;
let mut stream = stream;
let mut transferred = 0_usize;
let mut buffer = [0_u8; 65536];
while transferred < expected_length {
let wanted = buffer.len().min(expected_length - transferred);
let count = source.read_at(&mut buffer[..wanted], transferred as u64)?;
if count == 0 { return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "short source")); }
stream.write_all(&buffer[..count])?;
transferred += count;
}
שאר בדיקת הסיום נשארת זהה. יש למדוד בנפרד את לולאת ההעברה ואת הכנת המקור, משום שחישוב סכום הבדיקה וכתיבת הקובץ האנונימי יכולים להסתיר את ההבדל שרוצים למדוד. במערכת אמיתית נמדוד גם מצב מטמון, ולא נציג תוצאה של מקור שכבר נמצא בזיכרון כמדידה של קריאה מהתקן אחסון.
פתרון שני - היסט של הודעות¶
התוכנית הבאה משתמשת בתלות libc ומעבירה שתי הודעות קצרות בסוקט הודעות מקומי. הקריאה יכולה להחזיר מספר הודעות קטן מהמבוקש, ולכן ההיסט נשמר בין הניסיונות.
use std::io;
use std::os::fd::AsRawFd;
use std::os::unix::net::UnixDatagram;
fn main() -> io::Result<()> {
let (sender, receiver) = UnixDatagram::pair()?;
let payloads: [&[u8]; 2] = [b"one", b"two"];
let mut vectors = payloads.map(|bytes| libc::iovec {
iov_base: bytes.as_ptr().cast_mut().cast(), iov_len: bytes.len(),
});
let mut messages: [libc::mmsghdr; 2] = unsafe { std::mem::zeroed() };
for index in 0..2 {
messages[index].msg_hdr.msg_iov = &mut vectors[index];
messages[index].msg_hdr.msg_iovlen = 1;
}
let mut sent = 0;
while sent != messages.len() {
let count = unsafe { libc::sendmmsg(sender.as_raw_fd(), messages.as_mut_ptr().add(sent),
(messages.len() - sent) as u32, 0) };
if count > 0 { sent += count as usize; continue; }
if count == 0 { return Err(io::Error::new(io::ErrorKind::WriteZero, "no messages sent")); }
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted { return Err(error); }
}
for expected in payloads {
let mut buffer = [0_u8; 16];
let count = receiver.recv(&mut buffer)?;
assert_eq!(&buffer[..count], expected);
}
Ok(())
}
המצביעים מתייחסים למערכים שלא זזים לאורך הקריאה, והקרנל קורא את נתוני השליחה בלי לשמור אותם דרך המצביעים האלה לאחר חזרת הקריאה הרגילה. בממשק כללי שמחזיר שגיאה אחרי התקדמות קודמת צריך להחזיר למתקשר גם את ההיסט שנצבר, כדי שלא ישלח שוב הודעות שכבר נמסרו.
פתרון שלישי - החזרה לפי זהות השלמה¶
מאגר מתאים מחזיק לכל באפר מצב פנוי או פעיל, זהות פעולה ומספר הבתים שהפעולה משתמשת בהם. בזמן שליחה הבעלות עוברת מהמקצה לרשומת הפעולה. רק הודעת השלמה שמתאימה לזהות ולחוזה הממשק מאפשרת להחזיר את הבאפר לרשימת הפנויים.
בקשת ביטול יכולה להתחרות בהשלמה, וחזרת השליחה יכולה לציין רק שהבקשה התקבלה. לכן שני האירועים אינם משמשים כקיצור דרך לשחרור. אם ההשלמה מדווחת על טווח מזהים, מעדכנים בדיוק את הפעולות בטווח לפי כללי הגלישה של הממשק, ודוחים התאמה למזהה שכבר מוחזר או ממוחזר.
המדידה צריכה לכלול את מספר הבאפרים המרבי שהמתינו להחזרה. חיסכון בזמן העתקה שמחייב מאגר גדול בהרבה עשוי להיות בחירה טובה או גרועה בהתאם למערכת, אבל אי אפשר לראות את המחיר בלי למדוד אותו.