- ache,
Bonjour,
J’ai un petit problème en Rust.
Je cherche à obtenir les même performances que j’ai avec un programme en Go avec un nouveau programme écrit en Rust.
Le problème, c’est que c’est assez difficile.
Le programme est un serveur Web qui distribue de gros fichiers (ici 6.6Gio). J’utilise tokio et hyper.
Mon premier code ressemble à ça :
fn main() {
let addr = "[::1]:1337".parse().unwrap();
let service = || {
service_fn_ok( |_| {
let task = tokio::fs::File::open("big")
.and_then( |file| {
Response::new(Body::wrap_stream(TokioStream{ file: file,
buf: vec!(0; BUF_SIZE)
}))
})
match task.wait() {
Err(_) => Response::new(Body::from("Please (╥_╥)")),
Ok(s) => Response::new(Body::wrap_stream(s)),
}
})
};
let server = Server::bind(&addr)
.serve(service)
.map_err(|e| eprintln!("Error: {}", e));
hyper::rt::run(server);
}
Les performances étaient correctes. Mais bien en deçà de ce qu’offrait le programme Go.
J’ai un petit PC et les performances étaient de 230MB/s pour Rust contre ~420MB/s pour le programme en Go.
Après avoir cherché beaucoup d’amélioration, j’ai fini par trouver que sur le PC où je suis 420MB/s est à peu près la vitesse maximale obtenue à partir du disque dur (450MB/s) alors que le PC est capable d’envoyer virtuellement sur l’interface loopback @600MB/s.
J’ai codé un programme capable de faire les deux, lire à la vitesse maximale et envoyer @600MB/s.
J’aimerais idéalement que les deux arrivent à se synchroniser sans pour autant perdre en performance. Voilà le programme qui benchmark un peu les différentes ressources systèmes.
const BUF_SIZE : usize = 1<<19;
static mut BUF : [u8; BUF_SIZE] = [0; BUF_SIZE];
struct TokioStream {
}
impl Stream for TokioStream {
type Item = &'static[u8];
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
unsafe {
Ok(Async::Ready(Some(&BUF[..])))
}
}
}
static mut a :i64 = 0;
fn main() {
let start = PreciseTime::now();
let task = tokio::fs::File::open("big")
.and_then(|mut file| unsafe {
loop {
let r = file.poll_read(&mut BUF);
match r {
Ok(Async::Ready(0)) => {
break r
},
Ok(Async::Ready(size)) => {
a += size as i64;
//Ok(Async::Ready(Some(&BUF[..])));
continue
},
Ok(Async::NotReady) => {
break r
},
Err(_) => {
break r
},
}
}
}
).map(|_| {
;
}).map_err(|err| eprintln!("IO error: {:?}", err));
tokio::run(task);
let end = PreciseTime::now();
unsafe {
let dur : f64 = start.to(end).num_milliseconds() as f64 / 1000.;
let size : f64 = a as f64;
println!("{:.3}MB/s", (size / dur)/(1024.*1024.));
}
let addr = "[::1]:1337".parse().unwrap();
let service = || {
service_fn( |_| {
tokio::fs::File::open("big")
.and_then( |_| {
let fs = TokioStream{};
Ok(Response::new(Body::wrap_stream(fs)))
})
})
};
let server = Server::bind(&addr)
.serve(service)
.map_err(|e| eprintln!("Error: {}", e));
hyper::rt::run(server);
}
Je ne sais pas comment faire pour arriver à conserver les performances de chaque composant de manière à être limité par le moins rapide. Plusieurs buffers avec des Mutex le tous séparer dans des threads différents ? C’est basique, mais comment faire ? Il n’y a rien de prévu pour ça déjà ?
Merci et à bientôt \o
PS: Il est question d’un PC peu performant mais le disque dur est un SSD donc les performances de lecture sont tout à fait acceptable. Cependant, pour ce qui est du réseau, généralement en loopback, on trouvera plus souvent du GB/s. Un PC moderne et performant pourrait également obtenir des performances de l’ordre de 10GB/s si des phénomènes de cache interviennent.