/dev/posts/

Per-page shallow snapshots for the SimGrid model checker

Published:

Updated:

I looked at my options to achieve efficient/cheap snapshots of the simulated application for the Simgrid model checker using copy-on-write. Here I look at another solution to achieve this without using copy-on-write.

Checkpointing

Basic idea

The idea is to save each page of the state of the application independently: when a snapshot page is stored, the snapshoting logic first checks if a page with the same content is already stored in the snapshot pages:

The memory pages are only shared between the different snapshots but are never shared with the simulated application: copy-on-write is not used which means that the simulated application will not be slowned down by the unsharing page faults. As a result, the basic solution can be implemented purely in userspace.

The first snapshot will be a full snapshot. Other snapshots will usually be shallow: if 98% of the memory pages are not touched between successive snapshots, all those pages will be shared and only 2% of the pages will be copied in the second snapshot.

A hash of the content of the page can be used to limit the comparison of the new memory page with only a subset of the stored memory pages.

Better snapshots with soft-dirty page tracking

It is still necessary to scan and hash all the pages of the state of the simulated process each time a snapshot is done which seems to be quite inefficient. We can use the soft-dirty feature of the Linux kernel to detect which pages have been written since the previous snapshot and only try to store the modified ones.

After each snapshot, each page of the process is marked as soft-clean and protected against write. Each time a soft-clean page is touched, a page fault is raised: the kernel marks the page as soft-dirty and remove the protection on the page. On the next snapshot, it is possible to find which pages are soft-dirty (i.e. were modified since the previous snapshot) and only save those pages.

Incremental snapshot restoration

Even when restoring the state of a snapshot, we might use the soft-dirty information to avoid copying data which have not changed:

If a lot of pages do not change between snapshots, this technique reduces the number of pages which needs to be copied to restore a snapshot (and avoid the related soft-dirty page faults).

Memory address translation

Once an efficient snapshoting strategy is implemented, I expect that in many cases, most of the time will be spent in the state comparison code: we need to find a solution to avoid spending too much time translating between the addresses of the simulated application and the addresses of the snapshots.

Create a linear view of the areas of the snapshots

We might want to create a linear view of the areas of each snapshot of in order to have a simple code for the address translation:

find_snapshot_address(real_address, snapshot)
{
  memory_area             ← find_memory_area(real_address)
  offset                  ← real_address - memory_area.start
  snapshot_area           ← find_snapshot_area(memory_area)
  snapshot_address        ← snapshot_area.start + offset
  return snapshot_address
}

Moreover and probably more importantly, as long as we are in the same memory region, we can apply an offset in the real address translate into the same offset in the linear view of the snapshot. This case happens all the time when we are comparing the states:

In all thoses cases, we could apply a simple offset from the base snapshot address: if the memory pages of the snapshot are scattered in the virtual memory space, the model checker will have to apply the offset to the real base address and then translate the resulting address.

using non-linear memory mappings

One solution to create a linear view of a snapshot memory region, would be to use a non-linear memory mapping (remap_file_pages) of the snapshot memory:

However, one remap_file_pages() call will be necessary per each memory page so I do not expect this solution to be very promising unless a more efficient version of this system call is added in a later release of the Linux kernel.

Update: remap_file_page is deprecated.

using incremental snapshot reconstruction

Another solution, is to create a linear copy of the snapshot areas. We incrementally update those copies to reflect different snapshots but only updating the pages which are different between the different snapshots.

When we want to compare the current state against another one, we first have to recreate a linear view of the snapshot of the latter by copying in the linear view all the memory pages which are different from the previous view.

We want avoid reconstructing the state memory. This can be done by creating a global hash of the state of the simulated application based on key characteristics of the state (such as the numberf of processes, the instruction pointers of each process in its stack frame, etc.).

Software MMU

The other solution is to replicate the algorithm of the MMU in software to translate from virtual pages into file pages:

find_snapshot_address(real_address, snapshot)
{
  page_number             ← get_page_number(address)
  offset                  ← get_offset(address)
  snapshot_page_number    ← get_snapshot_page_number(snapshot, page_number)
  snapshot_page_address   ← get_page_address(snapshot_page_number)
  snapshot_address        ← snapshot_page_address + offset
  return snapshot_address
}

As I said earlier, this might impact the performance of the state comparison.

Other granularity

We might use another granularity instead of the page: for example we might snapshot at the malloc() granularity:

Conclusion

This approach seems quite promising:

It is not clear which variation will be the more efficient. I am probably going to implement the software MMU approach.