2010-05-18 13 views
12

Oye, me gustaría hacer esto lo más rápido posible porque se llama MUCHO en un programa que estoy escribiendo, entonces hay alguna forma más rápida de inicializar un vector de C++ a valores aleatorios que:Inicializando un vector de C++ a valores aleatorios ... rápido

double range;//set to the range of a particular function i want to evaluate. 
std::vector<double> x(30, 0.0); 
for (int i=0;i<x.size();i++) { 
    x.at(i) = (rand()/(double)RAND_MAX)*range; 
} 

EDITAR: solucionó el inicializador de x.

+0

El tipo correcto para acceder al elemento de datos de un vector por índice es 'std :: vector <...> :: size_type', no' int'. – sbi

+0

Supongo que los compiladores comunes moverán el 'range/RAND_MAX' del propio loop en sus optimizadores? – sbi

+3

Es curioso saber si un generador de perfiles ha identificado esto como un problema. – Puppy

Respuesta

17

En este momento, esto debería ser Realmente rápido ya que el bucle no se ejecutará.

En lo personal, yo probablemente usar algo como esto:

struct gen_rand { 
    double range; 
public: 
    gen_rand(double r=1.0) : range(r) {} 
    double operator()() { 
     return (rand()/(double)RAND_MAX) * range; 
    } 
}; 

std::vector<double> x(num_items); 
std::generate_n(x.begin(), num_items, gen_rand()); 

Editar: Es puramente un micro-optimización que puede hacer ninguna diferencia en absoluto, pero se podría considerar la reordenación de la computación para obtener algo así como :

struct gen_rand { 
    double factor; 
public: 
    gen_rand(double r=1.0) : factor(range/RAND_MAX) {} 
    double operator()() { 
     return rand() * factor; 
    } 
}; 

Por supuesto, hay una muy buena posibilidad de que el compilador ya se va a hacer esto (o algo equivalente) pero no se pierde nada con intentarlo de todos modos (aunque en realidad es probablemente sólo ayuda a la optimización apagado) .

Edit2: "OSE" (como suele ser el caso) que es correcto: es posible ganar un poco reservando inicialmente el espacio, a continuación, utilizando un iterador de inserción para colocar los datos en su lugar:

std::vector<double> x; 
x.reserve(num_items); 
std::generate_n(std::back_inserter(x), num_items, gen_rand()); 

Al igual que antes , estamos en esa optimización microscópica, no estoy del todo seguro de que realmente esperar para ver una diferencia en absoluto. En particular, dado que todo esto se hace con plantillas, hay una gran posibilidad (si no es que todas) de que el código se genere en línea. En ese caso, es probable que el optimizador advierta que todos los datos iniciales se sobreescriben y omita inicializarlos.

Al final, sin embargo, casi la única parte que es muy probable para hacer una diferencia significativa es deshacerse de la .at(i).Los otros podrían, pero con las optimizaciones activadas, realmente no esperaría que lo hicieran.

+1

No es justo, lo vi primero. Soy demasiado lento, me ganaste. – wheaties

+5

Esto todavía preinicializa el vector con valores '0.0' que luego se sobrescriben. ¿No eliminarían eso 'reserva()' e e insertar iterador? – sbi

+0

@Wheaties: esa debe ser la primera vez - con mi (pobre) tipeo, ¡por lo general es al revés! :-) –

5

Sí, mientras que x.at (i) hace la comprobación de límites, x [i] no lo hace. Además, su código es incorrecto ya que no pudo especificar el tamaño de x por adelantado. Debe usar std::vector<double> x(n), donde n es la cantidad de elementos que desea usar; de lo contrario, su ciclo allí nunca se ejecutará.

O bien, es posible que desee crear un iterador personalizado para generar valores aleatorios y rellenarlo con el iterador; porque el constructor std :: vector inicializará sus elementos, de todos modos, de modo que si tiene una clase de iterador personalizada que genera valores aleatorios, puede eliminar un pase sobre los elementos.

En cuanto a la implementación de un iterator de su cuenta, aquí es mi código no probado:

class random_iterator 
{ 
    public: 
     typedef std::input_iterator_tag iterator_category; 
     typedef double value_type; 
     typedef int difference_type; 
     typedef double* pointer; 
     typedef double& reference; 

     random_iterator() : _range(1.0), _count(0) {} 
     random_iterator(double range, int count) : 
             _range(range), _count(count) {} 
     random_iterator(const random_iterator& o) : 
             _range(o._range), _count(o._count) {} 
     ~random_iterator(){} 

     double operator*()const{ return ((rand()/(double)RAND_MAX) * _range); } 
     int operator-(const random_iterator& o)const{ return o._count-_count; } 
     random_iterator& operator++(){ _count--; return *this; } 
     random_iterator operator++(int){ random_iterator cpy(*this); _count--; return cpy; } 
     bool operator==(const random_iterator& o)const{ return _count==o._count; } 
     bool operator!=(const random_iterator& o)const{ return _count!=o._count; } 

    private: 
     double _range; 
     int _count; 
}; 

Con el código anterior, debería ser posible utilizar:

std::vector<double> x(random_iterator(range,number),random_iterator()); 

Dicho esto, el generar código para la otra solución dada es más simple, y francamente, simplemente llenaría explícitamente el vector sin recurrir a nada tan elegante como este ... pero es genial pensarlo.

+0

Bien. La idea de un iterador que crea valores aleatorios es sencillamente genial. –

+0

Oh, lo siento, lo acabo de escribir aquí, es más por el concepto, pero lo corregiré, ty. ¿Podría darme un ejemplo o dirigirme a donde puedo leer sobre cómo hacer mi propio iterador? Soy bastante nuevo en C++. Gracias – Flamewires

+0

@ Flamewires, sí ... necesitarías crear una clase que se ajuste a uno de los conceptos del iterador. Ver: http://www.sgi.com/tech/stl/Iterators.html. Si eres bastante nuevo en C++, entonces crear tu propio iterador podría no ser una gran idea en este momento, ya que involucra nociones tales como "rasgos de tipo", "plantillas", "especialización de plantilla", "conceptos" y " sobrecarga del operador ". Si está familiarizado con eso, entonces podría ser algo razonable de hacer. –

2

Puede considerar usar un generador de números pseudoaleatorio que dé salida como una secuencia. Como la mayoría de los PRNG solo proporcionan una secuencia de todos modos, será mucho más eficiente que simplemente llamar a rand() una y otra vez.

Pero entonces, creo que realmente necesito saber más sobre su situación.

  • ¿Por qué este fragmento de código se ejecuta tanto? ¿Puede reestructurar su código para evitar volver a generar datos aleatorios con tanta frecuencia?
  • ¿Qué tan grandes son tus vectores?
  • ¿Qué tan "bueno" debe ser su generador de números aleatorios? Las distribuciones de alta calidad tienden a ser más caras de calcular.
  • Si sus vectores son grandes, ¿está reutilizando su espacio de búfer, o lo está tirando y lo está reasignando a otro lugar? Crear nuevos vectores de cualquier manera es una excelente manera de destruir tu caché.
3
#include <iostream> 
#include <vector> 
#include <algorithm> 

struct functor { 
    functor(double v):val(v) {} 
    double operator()() const { 
     return (rand()/(double)RAND_MAX)*val; 
    } 
private: 
    double val; 
}; 

int main(int argc, const char** argv) { 
    const int size = 10; 
    const double range = 3.0f; 

    std::vector<double> dvec; 
    std::generate_n(std::back_inserter(dvec), size, functor(range)); 

    // print all 
    std::copy(dvec.begin(), dvec.end(), (std::ostream_iterator<double>(std::cout, "\n"))); 

    return 0; 
} 

опоздал :(

1
respuesta de

@Jerry ataúd se ve muy bueno otros dos pensamientos, sin embargo:.

  1. procesos en línea - Todos los de su vector de acceso será muy rápido, pero si la llamada a rand() está fuera de línea, la sobrecarga de la llamada de función podría dominar. Si ese es el caso, es posible que deba rodar su propio pseudorandom number generator.

  2. SIMD - Si va a rodar su propio PRNG, también puede hacer que calcule 2 dobles (o 4 flotadores) a la vez. Esto reducirá el número de conversiones int-to-float así como las multiplicaciones. Nunca lo intenté, pero aparentemente hay una versión SIMD del Mersenne Twister que es bastante buena. Un simple linear congruential generator podría ser lo suficientemente bueno también (y eso es probablemente lo que rand() ya está usando).

0

La forma en que pienso acerca de esto es un acercamiento de goma con la carretera.
En otras palabras, hay ciertas cosas mínimas que tienen que suceder, no hay que darle más vueltas, como por ejemplo:

  • la función rand() tiene que ser llamado N veces.

  • el resultado de rand() tiene que convertirse en doble y luego multiplicarse por algo.

  • los números resultantes deben almacenarse en elementos consecutivos de una matriz.

El objetivo es, como mínimo, hacer esas cosas.

Otras inquietudes, como si se usa o no un std::vector y los iteradores están bien, siempre y cuando no agreguen ningún ciclo extra. La manera más fácil de ver si agregan ciclos adicionales significativos es hacer un paso de un solo paso del código en el nivel de lenguaje ensamblador.

11

He estado usando el método de functor de Jerry Coffin durante algún tiempo, pero con la llegada de C++ 11, tenemos un montón de nuevas funcionalidades de números aleatorios. Para llenar una matriz con valores aleatorios float ahora podemos hacer algo como lo siguiente. . .

const size_t elements = 300; 
std::vector<float> y(elements);  
std::uniform_real_distribution<float> distribution(0.0f, 2.0f); //Values between 0 and 2 
std::mt19937 engine; // Mersenne twister MT19937 
auto generator = std::bind(distribution, engine); 
std::generate_n(y.begin(), elements, generator); 

Consulte la sección correspondiente del Wikipedia para más motores y distribuciones

1
int main() { 
    int size = 10; 
    srand(time(NULL)); 
    std::vector<int> vec(size); 
    std::generate(vec.begin(), vec.end(), rand); 

    std::vector<int> vec_2(size); 
    std::generate(vec_2.begin(), vec_2.end(), [](){ return rand() % 50;}) 
} 

Es necesario incluir vectorial, algoritmo, tiempo, cstdlib.