Wordpress - Should I use Pre Get Posts or WP_Query

pre_get_posts will run the same query, so both will take same time. But, If you utilize pre_get_posts action you will save one or more SQL queries. Right now, WordPress is running default query and then you run your query with this function which replace the results of the default query (resulting, default query is of no use). Below is how you can move your $args to

function custom_pre_get_posts($query, $posttype='dealers', $poststatus='publish', $paidvalue='1', $taxtype='any_default_value', $geo='any_default_value', $brands='any_default_value') {

    // filter your request here.
    if($query->is_category) {

        $args = array(
            'post_type' => $posttype,
            'post_status' => array($poststatus),
            'orderby' => 'rand',
            'posts_per_page' => 30,
            'meta_query' => array(
                array(
                    'key' => 'wpcf-paid',
                    'value' => array($paidvalue),
                    'compare' => 'IN',
                )
            ),
            'tax_query' => array(
                'relation' => 'AND',
                array(
                    'taxonomy' => $taxtype,
                    'field' => 'slug',
                    'terms' => $geo
                ),
                array(
                    'taxonomy' => 'brands',
                    'field' => 'slug',
                    'terms' => $brands
                )
            )
        );
        $query->query_vars = $args;
    }
}
add_action('pre_get_posts', 'custom_pre_get_posts');

Late answer as the most upvoted answer will break your query and simply isn't true in some major points.

The main WP_Query and it's filters

First, WordPress internally uses query_posts() (a thin wrapper around WP_Query that shouldn't be used in themes or plugins) to do a WP_Query. This WP_Query is acting as the main loop/query. This query will run through a lot of filters and actions until the actual SQL query string is built. One of those is pre_get_posts. Others are posts_clauses, posts_where, etc. that also allow you to intercept the query string building process.

An in depth look at what happens inside core

WordPress runs the wp() function (in wp-includes/functions.php), which calls $wp->main() ($wp is an object of class WP, which is defined in wp-includes/class-wp.php). This tells WordPress to:

  1. Parse the URL into a query specification using WP->parse_request() -- more on that below.
  2. Set all the is_ variables that are used by Conditional Tags using $wp_query->parse_query() ($wp_query is an object of class WP_Query, which is defined in wp-includes/query.php). Note that in spite of this function's name, in this case WP_Query->parse_query doesn't actually do any parsing for us, since that is done before-hand by WP->parse_request().
  3. Convert the query specification into a MySQL database query, and run the database query to get the list of posts, in function WP_Query->get_posts(). Save the posts in the $wp_query object to be used in the WordPress Loop.

Source Codex

Conclusion

If you really want to modify the main query, then you can use a wide variety of filters. Simply use $query->set( 'some_key', 'some_value' ); to change data there or use $query->get( 'some_key' ); to retrieve data to do conditional checks. This will save you from doing a second query, as you're altering the SQL query only.

If you have to do an additional query, then go with a WP_Query object. This will add another query to the DB.

Example

As answers always work better with an example, you here got one really nice one (props to Brad Touesnard), that simply extends the core object and therefore is pretty reusable (make a plugin out of it):

class My_Book_Query extends WP_Query
{
    function __construct( $args = array() )
    {
        // Forced/default args
        $args = array_merge( $args, array(
            'posts_per_page' => -1
        ) );

        add_filter( 'posts_fields', array( $this, 'posts_fields' ) );

        parent::__construct( $args );
    }

    public function posts_fields( $sql )
    {
        return "{$sql}, {$GLOBALS['wpdb']->terms}.name AS 'book_category'";
    }
}

You can then run your second/additional query like you can see in the following example. Don't forget to reset your query afterwards.

$book_query = new My_Book_Query();
if ( $book_query->have_posts() )
{
    while ( $book_query->have_posts() )
    {
        $book_query->the_post();
        # ...do stuff...
    } // endwhile;
    wp_reset_postdata();
} // endif;

Please check out the answers at When to use WP_query(), query_posts() and pre_get_posts.

It is a great ressource if you have any doubts in mind.