Live data from Hacker News

Comparison of C++17, Go, and Java for a next-generation sequencing tool

bmcbioinformatics.biomedcentral.com

91–100 of 190 posts

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#91
post #48
post #45

Earlier quoted context omitted.

They address that somewhat in the discussion section: > C++ provides many features for more explicit memory management than is possible with reference counting. For example, it provides allocators [35] to decouple memory management from handling of objects in containers. In principle, this may make it possible to use such an allocator to allocate temporary objects that are known to become obsolete during the dealloca…

Of course you can use better allocators; but it's faster to avoid dynamic allocation (e.g. by pointing to memory mapped from the input file by the OS) altogether. If they allocate memory for each flyspeck of a 200 GB file and also create and change a reference counter for it, nobody should be surprised about the low performance. Have a look what e.g. shared_ptr does behind the scenes.

Unless you're streaming, in which case mmap'ed access on Linux is generally slower than read/write. At least it was the last time we checked for the Ardour project (probably about 3 years ago).

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#92
post #60

Earlier quoted context omitted.

> elPrep is an open-ended software framework that allows for arbitrary combinations of different functional steps in a pipeline, like duplicate marking, sorting reads, replacing read groups, and so on; additionally, elPrep also accommodates functional steps provided by third-party tool writers. This openness makes it difficult to precisely determine the lifetime of allocated objects during a program run > Phase 1 all…

Well, one thing that jumps out immediately to me is that everything appears to be using shared_ptr. And by that I mean everything. Why is everything shared? What does it even mean to have a shared_ptr ? Arbitrary mixed writes to a string seems like a bad idea, so shouldn't that be unique_ptr? Or like this: auto alns = make_shared >>(); A shared_ptr to a deque of shared_ptrs? deque isn't thread-safe, why would it be s…

they heap allocate strings because their string_range implementation is a shared_ptr to the original string plus two indices. It might be somewhat worth it assuming the strings are large enough. But if most strings are small, passing them around by move-value would probably be an overall win. One would need to benchmark it.

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#93
post #39
post #36

Earlier quoted context omitted.

If either of those affects performance there are WAY too many memory allocations happening.

Sure, but I guess that's the point --- the GCs in Java and Go can handle any allocation pattern you throw at them reasonably well, but there's not, as far as I know, such a "one-size-fits-all" solution in C++ (not that it needs one.)

If you don't want to plan out proper (as in performant) data structures and memory management, C++ is indeed the wrong language. And judging by the stuff discussed in comments above, this is exactly what happened here.

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#94
post #60
post #6

I had a quick look at the C++ source code provided at https://github.com/ExaScience/elprep-bench/tree/master/cpp . As suspected, everything is dynamically allocated and no memory mapping (see e.g. http://man7.org/linux/man-pages/man2/mmap.2.html ) is used. No wonder this is slow and eats a lot of memory. At the moment I have no information about why this design was chosen, if there is a justification for it, or if th…

> elPrep is an open-ended software framework that allows for arbitrary combinations of different functional steps in a pipeline, like duplicate marking, sorting reads, replacing read groups, and so on; additionally, elPrep also accommodates functional steps provided by third-party tool writers. This openness makes it difficult to precisely determine the lifetime of allocated objects during a program run > Phase 1 all…

For objects allocated at phase 1 and not necessary after,

I would use std::monotonic_buffer_resource added in C++17. It's implementation is consists of just a pointer point to large contagious memory. You want a n bytes of memory? return the pointer while adding ptr += n. Deallocation do nothing. Destructing std::monotonic_buffer_resource object deallocate the memory. If the objects has trivial destructor, as that is the case based on glancing the code, this is very efficient.

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#95
post #48

Earlier quoted context omitted.

Of course you can use better allocators; but it's faster to avoid dynamic allocation (e.g. by pointing to memory mapped from the input file by the OS) altogether. If they allocate memory for each flyspeck of a 200 GB file and also create and change a reference counter for it, nobody should be surprised about the low performance. Have a look what e.g. shared_ptr does behind the scenes.

Unless you're streaming, in which case mmap'ed access on Linux is generally slower than read/write. At least it was the last time we checked for the Ardour project (probably about 3 years ago).

See https://en.wikipedia.org/wiki/SAM_(file_format)

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#96
post #69

After quickly glancing the code, I concluded that they wrote C++ like there is no static type. It seems they faithfully ported the very dynamic nature of their existing code to C++ without thinking. Like what is this? https://github.com/ExaScience/elprep-bench/blob/master/cpp/f... auto alns = any_cast >>>(data); So the data is sam_alignment type inside shared_ptr inside deque inside another share_ptr inside god forbi…

> They really like std::deque and use it everywhere even though the sizeof(T) is like a few dozen bytes at best so they should rather use std::vector. The data structure of deque is a list of array. while it can amortize the continious adding of the elements to front or back, since the element size is very small, they should rather use vector.

Are you saying that access patterns (push/pop front/back) never matter and std::vector is always better than std::deque if the element size is very small? I don't think that this is true in this generality.

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#97
post #6

I had a quick look at the C++ source code provided at https://github.com/ExaScience/elprep-bench/tree/master/cpp . As suspected, everything is dynamically allocated and no memory mapping (see e.g. http://man7.org/linux/man-pages/man2/mmap.2.html ) is used. No wonder this is slow and eats a lot of memory. At the moment I have no information about why this design was chosen, if there is a justification for it, or if th…

The problem with C++, for a lot of people, is not that it's impossible to do it right, but that it's easy to do it wrong.

What is easy is to write C++ as if it was Java. (You just stick to the standard containers and use smart pointers when allocating objects on the heap.)

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#98

Earlier quoted context omitted.

Except that this "atomic" operation over shared_ptr's in the C++ standard isn't actually required to be lock free, and is in fact not lock free in common stdlib implementations. So they're not actually gaining anything over, e.g. RwLock >> in Rust.

They are atomic in the sense that it is not possible to observe the intermediate states and they are not data races. It is extremely hard though to implement them in a lock-free way without a true DCAS (which pretty much no architecture implements). I think all implementations use spinlock pools. I guess transactional hardware could be used. This is one of the reasons why GC does make it easier to implement some lock…

> without a true DCAS (which pretty much no architecture implements)

Does x86-64's cmpxchg16b not qualify?

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#99
post #79

Earlier quoted context omitted.

It's implementation is buckets divided node based hash map. Theoretically, its order is good, but not that efficient in modern hardware where the memory access is heavily cached so the data locality is more important than saving the memory copy or memory size. Using the linked list to save some memory copy doesn't benefit at all and the overhead is far greater than simply copying the contagious large chunk of memory.…

> contagious normally I wouldn't point out an obvious autocorrect mistake, but this is really season-appropriate (or, better, inappropriate) :).

Oh that's... not intended really.

Re: Comparison of C++17, Go, and Java for a next-generation sequencing tool

#100
> To achieve good performance, it was therefore necessary to explicitly control how often and when the garbage collector would run to avoid needless interruptions of the main program, especially during parallel phases.

Why? This is a batch program, interruptions don't matter, only the end-to-end time does.

> The goal of elPrep is to simultaneously keep both the runtime and the memory use low.

Why? Keeping runtime low lets you get more work done. Keeping memory use low means what? They are using a machine with 384 GB RAM, make use of it.

Worth noting also that they used GCC 7.2.1, Go 1.9.5, and Java 10. That's a pretty old GCC.

They don't seem to explicitly select a GC with Java, so they'll be using G1. G1 is still not entirely mature. It got much faster between 9 and 10, and somewhat faster from 10 to 11. For a batch process like this, though, the parallel collector is still probably a better choice. Using a newer JDK and a different collector should give better performance - but admittedly, probably won't reduce heap usage.

Post reply on HN