0

I have defined the following structs with custom load/save methods using serde and bincode:

use std::{
    fs::File,
    io::{Read, Seek, SeekFrom, Write},
};

use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Histogram {
    pub bars: Vec<f64>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Histograms {
    pub num_bars: usize,
    pub num_histograms: usize,
    pub vec: Vec<Histogram>,
}

fn main() {
    println!("Hello, world!");
}

impl Histogram {
    pub fn new(num_bars: usize) -> Histogram {
        Histogram {
            bars: vec![0.0; num_bars],
        }
    }
}

impl Histograms {
    pub fn new(num_histograms: usize, num_bars: usize) -> Histograms {
        let histograms = Histograms {
            vec: vec![Histogram::new(num_bars); num_histograms],
            num_histograms,
            num_bars,
        };
        histograms
    }

    pub fn save(&self, filename: &str) {
        let mut file = File::create(format!("{}{}", "path", filename)).unwrap();
        let bin = bincode::serialize(&self).unwrap();
        Write::write_all(&mut file, &bin).unwrap();
    }

    pub fn load(filename: &str) -> Histograms {
        let mut file = File::open(format!("{}{}", "path", filename)).unwrap();
        let mut vec: Vec<u8> = vec![0; file.seek(SeekFrom::End(0)).unwrap() as usize];
        file.seek(SeekFrom::Start(0)).unwrap();
        file.read(&mut vec).unwrap();
        let result: Histograms = bincode::deserialize(&vec).unwrap();
        result
    }
}

The strange thing now is, that the following test shows me that save/load works properly if the length of the vec(member of histograms) is small, but it fails(i don't get any error, just the resulting histograms instance is wrong) with large values like for instance 10000000. Precisely i get a value of 5263431 from where on it is not correct anymore.

mod tests {
    use core::panic;

    use super::*;
    #[test]
    fn save_load_test() {
        let histogramms = Histograms::new(10000000, 50);
        histogramms.save("debug_test");
        let histogramms1 = Histograms::load("debug_test");
        assert_eq!(histogramms.vec.len(), histogramms1.vec.len());
        let mut failed = false;
        for i in 0..histogramms.vec.len() {
            if histogramms.vec[i].bars.len() != histogramms1.vec[i].bars.len() {
                failed = true;
                println!("first i that failed: {}", i);
                println!(
                    "histogramms.vec.bars.len: {} histogramms1.vec.bars.len: {}",
                    histogramms.vec[i].bars.len(),
                    histogramms1.vec[i].bars.len()
                );
                break;
            }
        }
        if failed {
            panic!()
        }
    }
}

Any ideas what is going wrong?

Foveng
  • 47
  • 5
  • 1
    Do you have other tests that open and write to the `debug_test` file? If so, they could be overwriting your test data - `cargo` runs tests in parallel by default. – user4815162342 Mar 22 '21 at 09:30
  • I do not have other tests that write to debug_test and also i run the test in isolation. – Foveng Mar 22 '21 at 09:48
  • 1
    Are you aware that running your code will use at least 10 GB of memory? This isn't necessarily a mistake, but may not be what you intend. – Sven Marnach Mar 22 '21 at 10:28
  • 2
    Also 5263431 * 50 * 8 = 2105372400 which is suspiciously close to 2GB, – Masklinn Mar 22 '21 at 10:32
  • 1
    The test passes on my laptop, so it's hard to tell what problem you are running into. – Sven Marnach Mar 22 '21 at 10:44
  • 1
    Peak memory usage is only about 9GB – would have expected more overhead for the deserialization. – Sven Marnach Mar 22 '21 at 10:48
  • 1
    @Masklinn If we add the pointer needed by the system allocator we get basically exactly two gigs: 5263431 * 408 = 2147479848, 2^31 = 2147483648 – Sven Marnach Mar 22 '21 at 10:50
  • I am aware it is a huge struct, that is intended. Interesting the test passes for you. Just to avoid any confusion: the test always "passes", the question is just if it prints "fist i that failed" – Foveng Mar 22 '21 at 10:53
  • I edited the code, so it will also fail now when appropriate. – Foveng Mar 22 '21 at 11:07
  • it also fails if i run it in main, also with --release. – Foveng Mar 22 '21 at 11:14
  • 1
    @Foveng given how pretty much bang-on to 2GB the failure is, could it be that you are compiling on or for 32b Windows (`i686`)? Or that for some reason you have a 2GB ulimit? – Masklinn Mar 22 '21 at 11:23
  • i am on Ubuntu, program with vs-code, and did not change anything as far as i know. While running the code i can observe in the system monitor, that memory usage goes from 3.1 to a max of 11.4 GiB. Also running it outside of vs-code does not help. Maybe i sort of can only load a file with maximum 2 GiB. – Foveng Mar 22 '21 at 11:34
  • if i type ulimit in the shell, it prints unlimited – Foveng Mar 22 '21 at 11:41
  • my generated debug_test file has a size of 4.1GiB – Foveng Mar 22 '21 at 11:44
  • The first file saved has a size of 4.1 GiB. If i then load it, just to directly save it again, the resulting file has a size of 2.2 GiB. I also just wrote a program which does the same thing just with an ordinary float vector. In this case both files have the same size of 4 GiB. So the problem seem probably to be code related? – Foveng Mar 22 '21 at 12:19
  • 1
    @Foveng I used `assert!(false);` instead of `break;` in you original test code, and the test passes. I can't reproduce your issue. – Sven Marnach Mar 22 '21 at 13:11
  • I run it on my windows drive, and it works properly. Only special thing going on with my linux is, that i have it encrypted, and that i have two hard drives in my pc, one with windows and one with linux. Guess if nobody else can reproduce this, i will just try to load it somehow differently, and this mystery will stay unsolved. Anyways, thanks everybody! – Foveng Mar 22 '21 at 14:09
  • @Foveng I actually _can_ reproduce the issue on Linux. (I apparetnly accidentally reverted the change that made the test properly fail, and the output was not shown since that's the default for tests. – Sven Marnach Mar 22 '21 at 15:19
  • hmm, hard to imagine that serde or bincode have such a big bug and nobody noticed. Maybe somebody will pass by here and knows the solution. Otherwise i might post it on their github. – Foveng Mar 22 '21 at 16:10
  • @Foveng It's actually a bug in your code. Read the documentation of `Read::read()` to find it. I'll post an answer shortly. – Sven Marnach Mar 22 '21 at 16:40

1 Answers1

5

The Read::read() function pulls some bytes from the source, but it is in no way guaranteed to read all of them. The only guarantee you get is that you will get some bytes if there are any left (at least if your buffer doesn't have length zero).

The easiest way to fix this is to use the std::fs::read() function instead:

pub fn load(filename: &str) -> Histograms {
    let vec = std::fs::read(format!("{}{}", "path", filename)).unwrap();
    bincode::deserialize(&vec).unwrap()
}

An even better fix is to directly deserialize from the file, without reading the whole file into memory first:

pub fn save(&self, filename: &str) {
    let mut w = BufWriter::new(File::create(format!("{}{}", "path", filename)).unwrap());
    bincode::serialize_into(&mut w, &self).unwrap();
    w.flush().unwrap();
}

pub fn load(filename: &str) -> Histograms {
    let file = File::open(format!("{}{}", "path", filename)).unwrap();
    bincode::deserialize_from(BufReader::new(file)).unwrap()
}

(Of course you should add proper error handling for real-world code.)

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Ahh, thats it:) your first solution with std::fs:read() works fine, the second though takes for ever, and than has the same error as before. Can you confirm that? If you change your answer, ill mark it as answered. – Foveng Mar 22 '21 at 17:31
  • Run it two more times with bincode::deserialize_from... and these two time it worked. However it took MUCH longer than std::fs::read, even with --release. Don't know if the first time i was misreading something, i will stay with fs::read, though. – Foveng Mar 22 '21 at 18:04
  • @Foveng I forgot to add buffering – calling out to the OS for every float we read from the file is expected to be slow. With buffering, this should perform similar to the version reading the whole file at once, but with lower peak memory usage. (The buffered I/O version may be slightly slower, but the difference in release mode should be small.) – Sven Marnach Mar 22 '21 at 19:10
  • Nice catch! Note however that dropping a `BufWriter` without flushing the data will [let errors pass silently](https://github.com/rust-lang/rust/blob/79e5814f4520f2c51b5307421db45cd82d134e76/library/std/src/io/buffered/bufwriter.rs#L22). This matters because write errors do tend to occur in practice, e.g. when disk is full. I'd recommend flushing explicitly, [like this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=30ae5ebcc4b9b075cfbc97e26d4bd5da). – user4815162342 Mar 23 '21 at 19:52
  • @user4815162342 Good point, forgot about that! – Sven Marnach Mar 24 '21 at 08:53