1997-02-19 13:59:07 +01:00
|
|
|
/*------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* geqo_main.c
|
2002-07-20 06:59:10 +02:00
|
|
|
* solution to the query optimization problem
|
1997-09-07 07:04:48 +02:00
|
|
|
* by means of a Genetic Algorithm (GA)
|
1997-02-19 13:59:07 +01:00
|
|
|
*
|
2003-08-04 04:40:20 +02:00
|
|
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1997-02-19 13:59:07 +01:00
|
|
|
*
|
2003-08-04 04:40:20 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_main.c,v 1.38 2003/08/04 02:39:59 momjian Exp $
|
1997-02-19 13:59:07 +01:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* contributed by:
|
|
|
|
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
|
1997-09-07 07:04:48 +02:00
|
|
|
* Martin Utesch * Institute of Automatic Control *
|
|
|
|
= = University of Mining and Technology =
|
|
|
|
* utesch@aut.tu-freiberg.de * Freiberg, Germany *
|
1997-02-19 13:59:07 +01:00
|
|
|
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* -- parts of this are adapted from D. Whitley's Genitor algorithm -- */
|
|
|
|
|
|
|
|
#include "postgres.h"
|
2000-05-31 02:28:42 +02:00
|
|
|
|
|
|
|
#include <time.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
1997-02-19 13:59:07 +01:00
|
|
|
#include "optimizer/geqo.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "optimizer/geqo_misc.h"
|
2002-07-20 06:59:10 +02:00
|
|
|
#include "optimizer/geqo_mutation.h"
|
1997-02-19 13:59:07 +01:00
|
|
|
#include "optimizer/geqo_pool.h"
|
|
|
|
#include "optimizer/geqo_selection.h"
|
|
|
|
|
|
|
|
|
2000-05-31 02:28:42 +02:00
|
|
|
/*
|
|
|
|
* Configuration options
|
|
|
|
*/
|
2001-03-22 05:01:46 +01:00
|
|
|
int Geqo_pool_size;
|
|
|
|
int Geqo_effort;
|
|
|
|
int Geqo_generations;
|
2000-05-31 02:28:42 +02:00
|
|
|
double Geqo_selection_bias;
|
2001-03-22 05:01:46 +01:00
|
|
|
int Geqo_random_seed;
|
2000-05-31 02:28:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
static int gimme_pool_size(int nr_rel);
|
|
|
|
static int gimme_number_generations(int pool_size, int effort);
|
|
|
|
|
1997-02-19 13:59:07 +01:00
|
|
|
/* define edge recombination crossover [ERX] per default */
|
|
|
|
#if !defined(ERX) && \
|
1997-09-07 07:04:48 +02:00
|
|
|
!defined(PMX) && \
|
|
|
|
!defined(CX) && \
|
|
|
|
!defined(PX) && \
|
|
|
|
!defined(OX1) && \
|
|
|
|
!defined(OX2)
|
1997-02-19 13:59:07 +01:00
|
|
|
#define ERX
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
1999-02-14 00:22:53 +01:00
|
|
|
* geqo
|
1997-09-07 07:04:48 +02:00
|
|
|
* solution of the query optimization problem
|
|
|
|
* similar to a constrained Traveling Salesman Problem (TSP)
|
1997-02-19 13:59:07 +01:00
|
|
|
*/
|
|
|
|
|
1998-07-18 06:22:52 +02:00
|
|
|
RelOptInfo *
|
2000-09-19 20:42:34 +02:00
|
|
|
geqo(Query *root, int number_of_rels, List *initial_rels)
|
1997-02-19 13:59:07 +01:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
int generation;
|
|
|
|
Chromosome *momma;
|
|
|
|
Chromosome *daddy;
|
|
|
|
Chromosome *kid;
|
1999-05-17 02:25:34 +02:00
|
|
|
Pool *pool;
|
|
|
|
int pool_size,
|
|
|
|
number_generations,
|
|
|
|
status_interval;
|
|
|
|
Gene *best_tour;
|
|
|
|
RelOptInfo *best_rel;
|
1999-05-25 18:15:34 +02:00
|
|
|
|
1997-03-14 17:03:02 +01:00
|
|
|
#if defined(ERX)
|
1997-09-08 04:41:22 +02:00
|
|
|
Edge *edge_table; /* list of edges */
|
|
|
|
int edge_failures = 0;
|
|
|
|
float difference;
|
1997-09-07 07:04:48 +02:00
|
|
|
#endif
|
1997-03-14 17:03:02 +01:00
|
|
|
#if defined(CX) || defined(PX) || defined(OX1) || defined(OX2)
|
1997-09-08 04:41:22 +02:00
|
|
|
City *city_table; /* list of cities */
|
1997-02-19 15:52:06 +01:00
|
|
|
#endif
|
|
|
|
#if defined(CX)
|
1997-09-08 04:41:22 +02:00
|
|
|
int cycle_diffs = 0;
|
|
|
|
int mutations = 0;
|
1997-02-19 15:52:06 +01:00
|
|
|
#endif
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* set GA parameters */
|
2000-05-31 02:28:42 +02:00
|
|
|
pool_size = gimme_pool_size(number_of_rels);
|
|
|
|
number_generations = gimme_number_generations(pool_size, Geqo_effort);
|
1997-09-07 07:04:48 +02:00
|
|
|
status_interval = 10;
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* seed random number generator */
|
2000-05-31 02:28:42 +02:00
|
|
|
/* XXX why is this done every time around? */
|
2001-03-22 05:01:46 +01:00
|
|
|
if (Geqo_random_seed >= 0)
|
|
|
|
srandom((unsigned int) Geqo_random_seed);
|
|
|
|
else
|
|
|
|
srandom((unsigned int) time(NULL));
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* allocate genetic pool memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
pool = alloc_pool(pool_size, number_of_rels);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* random initialization of the pool */
|
2000-09-19 20:42:34 +02:00
|
|
|
random_init_pool(root, initial_rels, pool, 0, pool->size);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* sort the pool according to cheapest path as fitness */
|
1997-09-07 07:04:48 +02:00
|
|
|
sort_pool(pool); /* we have to do it only one time, since
|
|
|
|
* all kids replace the worst individuals
|
|
|
|
* in future (-> geqo_pool.c:spread_chromo
|
|
|
|
* ) */
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
/* allocate chromosome momma and daddy memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
momma = alloc_chromo(pool->string_length);
|
|
|
|
daddy = alloc_chromo(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
#if defined (ERX)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using edge recombination crossover [ERX]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate edge table memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
edge_table = alloc_edge_table(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PMX)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using partially matched crossover [PMX]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate chromosome kid memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = alloc_chromo(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(CX)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using cycle crossover [CX]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate city table memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = alloc_chromo(pool->string_length);
|
|
|
|
city_table = alloc_city_table(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PX)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using position crossover [PX]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate city table memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = alloc_chromo(pool->string_length);
|
|
|
|
city_table = alloc_city_table(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX1)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using order crossover [OX1]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate city table memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = alloc_chromo(pool->string_length);
|
|
|
|
city_table = alloc_city_table(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX2)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(DEBUG2, "using order crossover [OX2]");
|
1997-02-19 13:59:07 +01:00
|
|
|
/* allocate city table memory */
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = alloc_chromo(pool->string_length);
|
|
|
|
city_table = alloc_city_table(pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/* my pain main part: */
|
|
|
|
/* iterative optimization */
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
for (generation = 0; generation < number_generations; generation++)
|
|
|
|
{
|
2002-07-20 06:59:10 +02:00
|
|
|
/* SELECTION: using linear bias function */
|
|
|
|
geqo_selection(momma, daddy, pool, Geqo_selection_bias);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
#if defined (ERX)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* EDGE RECOMBINATION CROSSOVER */
|
|
|
|
difference = gimme_edge_table(momma->string, daddy->string, pool->string_length, edge_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
kid = momma;
|
1997-02-19 13:59:07 +01:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/* are there any edge failures ? */
|
|
|
|
edge_failures += gimme_tour(edge_table, kid->string, pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PMX)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* PARTIALLY MATCHED CROSSOVER */
|
|
|
|
pmx(momma->string, daddy->string, kid->string, pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(CX)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* CYCLE CROSSOVER */
|
1999-02-03 22:18:02 +01:00
|
|
|
cycle_diffs = cx(momma->string, daddy->string, kid->string, pool->string_length, city_table);
|
1997-09-07 07:04:48 +02:00
|
|
|
/* mutate the child */
|
|
|
|
if (cycle_diffs == 0)
|
|
|
|
{
|
|
|
|
mutations++;
|
|
|
|
geqo_mutation(kid->string, pool->string_length);
|
|
|
|
}
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PX)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* POSITION CROSSOVER */
|
|
|
|
px(momma->string, daddy->string, kid->string, pool->string_length, city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX1)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* ORDER CROSSOVER */
|
|
|
|
ox1(momma->string, daddy->string, kid->string, pool->string_length, city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX2)
|
1997-09-07 07:04:48 +02:00
|
|
|
/* ORDER CROSSOVER */
|
|
|
|
ox2(momma->string, daddy->string, kid->string, pool->string_length, city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/* EVALUATE FITNESS */
|
2000-09-19 20:42:34 +02:00
|
|
|
kid->worth = geqo_eval(root, initial_rels,
|
|
|
|
kid->string, pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/* push the kid into the wilderness of life according to its worth */
|
|
|
|
spread_chromo(kid, pool);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
#ifdef GEQO_DEBUG
|
1997-09-07 07:04:48 +02:00
|
|
|
if (status_interval && !(generation % status_interval))
|
|
|
|
print_gen(stdout, pool, generation);
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
2002-07-20 06:59:10 +02:00
|
|
|
}
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
#if defined(ERX) && defined(GEQO_DEBUG)
|
1997-09-07 07:04:48 +02:00
|
|
|
if (edge_failures != 0)
|
Commit to match discussed elog() changes. Only update is that LOG is
now just below FATAL in server_min_messages. Added more text to
highlight ordering difference between it and client_min_messages.
---------------------------------------------------------------------------
REALLYFATAL => PANIC
STOP => PANIC
New INFO level the prints to client by default
New LOG level the prints to server log by default
Cause VACUUM information to print only to the client
NOTICE => INFO where purely information messages are sent
DEBUG => LOG for purely server status messages
DEBUG removed, kept as backward compatible
DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1 added
DebugLvl removed in favor of new DEBUG[1-5] symbols
New server_min_messages GUC parameter with values:
DEBUG[5-1], INFO, NOTICE, ERROR, LOG, FATAL, PANIC
New client_min_messages GUC parameter with values:
DEBUG[5-1], LOG, INFO, NOTICE, ERROR, FATAL, PANIC
Server startup now logged with LOG instead of DEBUG
Remove debug_level GUC parameter
elog() numbers now start at 10
Add test to print error message if older elog() values are passed to elog()
Bootstrap mode now has a -d that requires an argument, like postmaster
2002-03-02 22:39:36 +01:00
|
|
|
elog(LOG, "[GEQO] failures: %d, average: %d",
|
2001-06-03 16:53:56 +02:00
|
|
|
edge_failures, (int) generation / edge_failures);
|
1997-09-07 07:04:48 +02:00
|
|
|
else
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(LOG, "[GEQO] no edge failures detected");
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(CX) && defined(GEQO_DEBUG)
|
1997-09-07 07:04:48 +02:00
|
|
|
if (mutations != 0)
|
Commit to match discussed elog() changes. Only update is that LOG is
now just below FATAL in server_min_messages. Added more text to
highlight ordering difference between it and client_min_messages.
---------------------------------------------------------------------------
REALLYFATAL => PANIC
STOP => PANIC
New INFO level the prints to client by default
New LOG level the prints to server log by default
Cause VACUUM information to print only to the client
NOTICE => INFO where purely information messages are sent
DEBUG => LOG for purely server status messages
DEBUG removed, kept as backward compatible
DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1 added
DebugLvl removed in favor of new DEBUG[1-5] symbols
New server_min_messages GUC parameter with values:
DEBUG[5-1], INFO, NOTICE, ERROR, LOG, FATAL, PANIC
New client_min_messages GUC parameter with values:
DEBUG[5-1], LOG, INFO, NOTICE, ERROR, FATAL, PANIC
Server startup now logged with LOG instead of DEBUG
Remove debug_level GUC parameter
elog() numbers now start at 10
Add test to print error message if older elog() values are passed to elog()
Bootstrap mode now has a -d that requires an argument, like postmaster
2002-03-02 22:39:36 +01:00
|
|
|
elog(LOG, "[GEQO] mutations: %d, generations: %d", mutations, generation);
|
1997-09-07 07:04:48 +02:00
|
|
|
else
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(LOG, "[GEQO] no mutations processed");
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1997-02-19 13:59:07 +01:00
|
|
|
#ifdef GEQO_DEBUG
|
1997-09-07 07:04:48 +02:00
|
|
|
print_pool(stdout, pool, 0, pool_size - 1);
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2003-01-20 19:55:07 +01:00
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* got the cheapest query tree processed by geqo; first element of the
|
|
|
|
* population indicates the best query tree
|
2003-01-20 19:55:07 +01:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
best_tour = (Gene *) pool->data[0].string;
|
1997-02-19 13:59:07 +01:00
|
|
|
|
2003-01-20 19:55:07 +01:00
|
|
|
/* root->join_rel_list will be modified during this ! */
|
2000-09-29 20:21:41 +02:00
|
|
|
best_rel = gimme_tree(root, initial_rels,
|
Fix GEQO to work again in CVS tip, by being more careful about memory
allocation in best_inner_indexscan(). While at it, simplify GEQO's
interface to the main planner --- make_join_rel() offers exactly the
API it really wants, whereas calling make_rels_by_clause_joins() and
make_rels_by_clauseless_joins() required jumping through hoops.
Rewrite gimme_tree for clarity (sometimes iteration is much better than
recursion), and approximately halve GEQO's runtime by recognizing that
tours of the forms (a,b,c,d,...) and (b,a,c,d,...) are equivalent
because of symmetry in make_join_rel().
2002-12-16 22:30:30 +01:00
|
|
|
best_tour, pool->string_length);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
2003-01-20 19:55:07 +01:00
|
|
|
if (best_rel == NULL)
|
2003-07-25 02:01:09 +02:00
|
|
|
elog(ERROR, "failed to make a valid plan");
|
2003-01-20 19:55:07 +01:00
|
|
|
|
|
|
|
/* DBG: show the query plan */
|
|
|
|
#ifdef NOT_USED
|
|
|
|
print_plan(best_plan, root);
|
|
|
|
#endif
|
1997-02-19 13:59:07 +01:00
|
|
|
|
2003-01-20 19:55:07 +01:00
|
|
|
/* ... free memory stuff */
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(momma);
|
|
|
|
free_chromo(daddy);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
|
|
|
#if defined (ERX)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_edge_table(edge_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PMX)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(kid);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(CX)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(kid);
|
|
|
|
free_city_table(city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(PX)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(kid);
|
|
|
|
free_city_table(city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX1)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(kid);
|
|
|
|
free_city_table(city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#elif defined(OX2)
|
1997-09-07 07:04:48 +02:00
|
|
|
free_chromo(kid);
|
|
|
|
free_city_table(city_table);
|
1997-02-19 13:59:07 +01:00
|
|
|
#endif
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
free_pool(pool);
|
1997-02-19 13:59:07 +01:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
return best_rel;
|
1997-02-19 13:59:07 +01:00
|
|
|
}
|
2000-05-31 02:28:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return either configured pool size or
|
|
|
|
* a good default based on query size (no. of relations)
|
|
|
|
* = 2^(QS+1)
|
|
|
|
* also constrain between 128 and 1024
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
gimme_pool_size(int nr_rel)
|
|
|
|
{
|
|
|
|
double size;
|
|
|
|
|
2001-03-22 05:01:46 +01:00
|
|
|
if (Geqo_pool_size != 0)
|
2002-07-20 06:59:10 +02:00
|
|
|
return Geqo_pool_size;
|
2000-05-31 02:28:42 +02:00
|
|
|
|
|
|
|
size = pow(2.0, nr_rel + 1.0);
|
|
|
|
|
|
|
|
if (size < MIN_GEQO_POOL_SIZE)
|
|
|
|
return MIN_GEQO_POOL_SIZE;
|
|
|
|
else if (size > MAX_GEQO_POOL_SIZE)
|
|
|
|
return MAX_GEQO_POOL_SIZE;
|
|
|
|
else
|
|
|
|
return (int) ceil(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return either configured number of generations or
|
|
|
|
* some reasonable default calculated on the fly.
|
|
|
|
* = Effort * Log2(PoolSize)
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
gimme_number_generations(int pool_size, int effort)
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
if (Geqo_generations <= 0)
|
|
|
|
return effort * (int) ceil(log((double) pool_size) / log(2.0));
|
|
|
|
else
|
|
|
|
return Geqo_generations;
|
2000-05-31 02:28:42 +02:00
|
|
|
}
|