Hi,
There are three areas where the overhead of msync can make a difference:
1. Small flushes get rounded up to large flushes. For example, imagine you are implementing a btree where you are updating pointers during inserts, rebalancing, etc. Each time you want to ensure an update has been made persistent, you flush some small ranges, like a few cache lines. Each of those flushes gets rounded up to a page since that's the smallest granularity msync can handle. The impact is not just the extra flushes, but also the side effect of those flushes which invalidates those extra cache lines, forcing cache misses when they are next accessed.
2. No way to tell msync that non-temporal stores were used for parts of the range. In PMDK, you'll see lots of places where we use non-temporal stores for performance, and those stores "go around" the CPU caches. For the kind of fine-grained changes I mentioned above, PMDK knows that only normal stores need flushing, and any ranges where NT stores were used can be considered persistent. Note that even without PMDK in the picture, libc's memcpy will sometimes use NT stores for large ranges. You have no visibility into that, so you must call msync to make changes persistent, even when there's no flush required due to NT stores.
3. The overhead of kernel sys calls. msync can take locks, which can end up serializing highly multithreaded code. And the loop done by pmem_persist() will be a shorter overall code path. This is even more true on future platforms where CPU caches can be considered persistent. PMDK applications will detect these platforms and skip the flushes, but msync is still a trip through the kernel.
Of course, it depends on the use case. For flushing large ranges where the kernel entry/exit is a very small portion of the code, and assuming no lock contention and no NT stores, pmem_persist and msync will perform about the same. And also please remember that you can only use pmem_persist when pmem_is_pmem returns true, meaning the file system has agreed to make it safe by granting mmap with the MAP_SYNC flag.
On your second question, if you do not use msync at all and understand that a power failure or similar crash that can't flush everything will leave your data in an inconsistent state, then you could theoretically skip the msync calls, but I would recommend at least adding logic to your program to detect normal program shutdown and flush during it. For example, when the program shuts down normally, call msync or pmem_persist on any pmem that is potentially dirty, and then set some sort of "clean flag" and flush it to indicate the consistent state to the program next time it starts up.
-andy