The two are usually described as interchangeable and they are not: get_posts sets four arguments differently, and three of them change behaviour.
// get_posts() applies, whether you asked or not:
// 'suppress_filters' => true ← pre_get_posts does NOT run
// 'no_found_rows' => true ← pagination is unavailable
// 'ignore_sticky_posts' => true
// 'post_status' => 'publish'
// which is why a plugin filtering queries has no effect here
$posts = get_posts( array( 'suppress_filters' => false ) );
suppress_filters is the one that produces the confusing bug: a multilingual plugin or a visibility filter hooked to pre_get_posts is bypassed entirely, so get_posts returns content the site is supposed to be hiding. That is a correctness problem rather than a performance one. The defaults are otherwise sensible for a simple fetch, which is what it exists for — anything with pagination or filtering wants WP_Query.