Use PHP code to create custom user roles. Call it once?

Generally, you would call this code in a plugin activation hook or at the very least, init. The issue you may run into is calling this code too early if it’s outside any specific WordPress hooks which would cause issues. That being said, add_role() calls WP_Roles::add_role() and one of the first things it does is check if it’s already defined via:

if( empty( $role ) || isset( $this->roles[ $role ] ) ) {
    return;
}

WordPress saves roles in the options table and grabs those roles on instantiation. If the role is already set, it’s just going to return early no harm, no foul.