Complete.Org: Mailing Lists: Archives: freeciv-dev: January 2004:
[Freeciv-Dev] (PR#7342) cm patch: heap-allocate the combinations
Home

[Freeciv-Dev] (PR#7342) cm patch: heap-allocate the combinations

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] (PR#7342) cm patch: heap-allocate the combinations
From: "Benoit Hudson" <bh@xxxxxxxxxxxxxxxxxxx>
Date: Thu, 29 Jan 2004 13:41:17 -0800
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=7342 >

Here's the patch.  Only difference: the #if stuff discussed earlier.

Index: cm.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/aicore/cm.c,v
retrieving revision 1.19
diff -b -c -3 -p -u -r1.19 cm.c
--- cm.c        2004/01/10 09:41:31     1.19
+++ cm.c        2004/01/29 21:33:07
@@ -145,6 +145,7 @@
 #define SHOW_EXPAND_CACHE3_RESULT                       FALSE
 #define SHOW_CACHE_STATS                                FALSE
 #define SHOW_TIME_STATS                                 FALSE
+#define SHOW_CM_STORAGE_USED                            FALSE
 #define DISABLE_CACHE3                                  FALSE
 
 #define NUM_SPECIALISTS_ROLES                          3
@@ -172,13 +173,20 @@ static struct {
 /* 
  * Contains all combinations. Caches all the data about a city across
  * multiple cm_query_result calls about the same city.
+ * struct combination:
+ *      store one allocation of some number of workers to fields; if there
+ *      is leftover population, store all allocations of leftovers to
+ *      specialists
+ * struct results_set:
+ *      store all combinations for a given number of workers
+ * struct cache3:
+ *      store a results_set for each number of workers
  */
 static struct {
   int fields_available_total;
 
-  struct {
+  struct results_set {
     struct combination {
-      bool is_valid;
       int max_scientists, max_taxmen, worker;
       int production2[NUM_PRIMARY_STATS];
       enum city_tile_type worker_positions[CITY_MAP_SIZE][CITY_MAP_SIZE];
@@ -189,7 +197,9 @@ static struct {
        */
       struct cm_result *cache1;
       struct cm_result all_entertainer;
-    } combinations[MAX_COMBINATIONS];
+    } **combinations;
+    int ncombinations; /* number of valid combinations */
+    int ncombinations_allocated; /* amount of room in combinations array */
   } results[MAX_FIELDS_USED + 1];
 
   struct city *pcity;
@@ -255,17 +265,7 @@ static int count_worker(struct city *pci
 *****************************************************************************/
 static int count_valid_combinations(int fields_used)
 {
-  int i, result = 0;
-
-  for (i = 0; i < MAX_COMBINATIONS; i++) {
-    struct combination *current =
-       &cache3.results[fields_used].combinations[i];
-
-    if (current->is_valid) {
-      result++;
-    }
-  }
-  return result;
+  return cache3.results[fields_used].ncombinations;
 }
 
 /****************************************************************************
@@ -440,8 +440,6 @@ static void print_result(struct city *pc
 static void print_combination(struct city *pcity,
                              struct combination *combination)
 {
-  assert(combination->is_valid);
-
   freelog(LOG_NORMAL, "combination:  workers at:");
   my_city_map_iterate(pcity, x, y) {
     if (combination->worker_positions[x][y] == C_TILE_WORKER) {
@@ -455,7 +453,18 @@ static void print_combination(struct cit
          combination->production2[TRADE]);
 }
 
+
 /****************************************************************************
+ Eliminate all the storage for which this combination is responsible.
+ At the moment, that is the cache1 and the combination itself.
+*****************************************************************************/
+static void free_combination(struct combination *combination)
+{
+  free(combination->cache1);
+  free(combination);
+}
+
+/****************************************************************************
  Copy the current production stats and happy status of the given city
  to the result.
 *****************************************************************************/
@@ -581,22 +590,20 @@ static void update_cache2(struct city *p
 }
 
 /****************************************************************************
-...
+Clear the results of the cache.  Does NOT free all storage; arrays stay
+allocated so we don't thrash memory.
 *****************************************************************************/
 static void clear_cache(void)
 {
   int i, j;
   for (i = 0; i < MAX_FIELDS_USED + 1; i++) {
-    for (j = 0; j < MAX_COMBINATIONS; j++) {
-      if (!cache3.results[i].combinations[j].is_valid) {
-       continue;
-      }
-      if (cache3.results[i].combinations[j].cache1) {
-       free(cache3.results[i].combinations[j].cache1);
-       cache3.results[i].combinations[j].cache1 = NULL;
+    struct results_set *results = &cache3.results[i];
+    for (j = 0; j < results->ncombinations; j++) {
+      free_combination(results->combinations[j]);
+      results->combinations[j] = NULL;
       }
+    results->ncombinations = 0;
     }
-  }
   cache3.pcity = NULL;
 }
 
@@ -744,6 +751,26 @@ static void report_one_cache_stat(struct
 }
 #endif
 
+/****************************************************************************
+ Calculates how much storage is used by the CM.
+ Only do so if the define is set to TRUE (default is FALSE).
+*****************************************************************************/
+static void report_cm_storage_used(void)
+{
+#if SHOW_CM_STORAGE_USED
+  int i, sum=0;
+  for(i=0; i<=MAX_FIELDS_USED; ++i) {
+    sum += cache3.results[i].ncombinations_allocated
+      * sizeof(struct combination*);
+    sum += cache3.results[i].ncombinations
+      * sizeof(struct combination);
+  }
+  sum += sizeof(cache3);
+  freelog(LOG_NORMAL, "CM: cache3 uses %d bytes", sum);
+  /* we should compute the cache1 and cache2 usage as well. */
+#endif
+}
+
 
 /****************************************************************************
  Prints the data of the stats struct via freelog(LOG_NORMAL,...).
@@ -762,6 +789,8 @@ static void report_stats(void)
   report_one_cache_stat(&stats.cache2, "CACHE2");
   report_one_cache_stat(&stats.cache3, "CACHE3");
 #endif
+
+  report_cm_storage_used();
 }
 
 /****************************************************************************
@@ -779,8 +808,6 @@ static void fill_out_result(struct city 
   struct cm_result *slot;
   bool got_all;
 
-  assert(base_combination->is_valid);
-
   /*
    * First try to get a filled out result from cache1 or from the
    * all_entertainer result.
@@ -917,22 +944,12 @@ static void fill_out_result(struct city 
 static void add_combination(int fields_used,
                            struct combination *combination)
 {
-  static int max_used = 0;
-  int i, used;
-  /* This one is cached for later. Avoids another loop. */
-  struct combination *invalid_slot_for_insert = NULL;
+  int i;
+  struct results_set *results = &cache3.results[fields_used];
 
   /* Try to find a better combination. */
-  for (i = 0; i < MAX_COMBINATIONS; i++) {
-    struct combination *current =
-       &cache3.results[fields_used].combinations[i];
-
-    if (!current->is_valid) {
-      if (!invalid_slot_for_insert) {
-       invalid_slot_for_insert = current;
-      }
-      continue;
-    }
+  for (i = 0; i < results->ncombinations; i++) {
+    struct combination *current = results->combinations[i];
 
     if (current->production2[FOOD] >= combination->production2[FOOD] &&
        current->production2[SHIELD] >= combination->production2[SHIELD] &&
@@ -954,14 +971,9 @@ static void add_combination(int fields_u
      freelog(LOG_NORMAL, "add_combination()");
      print_combination(combination);
    */
-
-  for (i = 0; i < MAX_COMBINATIONS; i++) {
-    struct combination *current =
-       &cache3.results[fields_used].combinations[i];
 
-    if (!current->is_valid) {
-      continue;
-    }
+  for (i = 0; i < results->ncombinations; i++) {
+    struct combination *current = results->combinations[i];
 
     if (current->production2[FOOD] <= combination->production2[FOOD] &&
        current->production2[SHIELD] <= combination->production2[SHIELD] &&
@@ -970,12 +982,25 @@ static void add_combination(int fields_u
          freelog(LOG_NORMAL, "the following is now obsolete:");
          print_combination(current);
        */
-      current->is_valid = FALSE;
+
+      /* free it and remove it from the list */
+      free_combination(current);
+      results->combinations[i] =
+        results->combinations[results->ncombinations-1];
+      results->ncombinations--;
+      i--; /* then we do i++ , so we end up at the same index again */
     }
   }
 
-  /* Insert the given combination. */
-  if (invalid_slot_for_insert == NULL) {
+  /* Insert the given combination. Grow the array if needed. */
+  if (results->ncombinations >= results->ncombinations_allocated) {
+    if(results->ncombinations_allocated == 0) {
+      results->ncombinations_allocated = 1;
+    } else {
+      /* Assert that we don't grow too much (as a result of a bug,
+       * presumably).  Cases have been seen where ncombinations
+       * can be 100, but not 150. */
+      if(results->ncombinations_allocated >= MAX_COMBINATIONS) {
     freelog(LOG_FATAL,
            "No more free combinations left. You may increase "
            "MAX_COMBINATIONS or \nreport this error to "
@@ -984,19 +1009,22 @@ static void add_combination(int fields_u
     exit(EXIT_FAILURE);
   }
 
-  memcpy(invalid_slot_for_insert, combination, sizeof(struct combination));
-  invalid_slot_for_insert->all_entertainer.found_a_valid = FALSE;
-  invalid_slot_for_insert->cache1 = NULL;
-
-  used = count_valid_combinations(fields_used);
-  if (used > (MAX_COMBINATIONS * 9) / 10
-      && (used > max_used || max_used == 0)) {
-    max_used = used;
-    freelog(LOG_ERROR,
-           "Warning: there are currently %d out of %d combinations used",
-           used, MAX_COMBINATIONS);
+      /* Double for amortized constant-time growing. */
+      results->ncombinations_allocated *= 2;
+    }
+    results->combinations = (struct combination**)fc_realloc(
+        results->combinations,
+        results->ncombinations_allocated*sizeof(struct combination*));
   }
 
+  /* finally, actually insert it. */
+  i = results->ncombinations;
+  results->combinations[i] = fc_malloc(sizeof(struct combination));
+  memcpy(results->combinations[i], combination, sizeof(struct combination));
+  results->combinations[i]->all_entertainer.found_a_valid = FALSE;
+  results->combinations[i]->cache1 = NULL;
+  results->ncombinations++;
+
   freelog(LOG_DEBUG, "there are now %d combination which use %d tiles",
          count_valid_combinations(fields_used), fields_used);
 }
@@ -1004,11 +1032,15 @@ static void add_combination(int fields_u
 /****************************************************************************
  Will create combinations which use (fields_to_use) fields from the
  combinations which use (fields_to_use-1) fields.
+ Precondition: cache3.results[fields_to_use-1] is valid.
+               cache3.results[fields_to_use]   is empty.
 *****************************************************************************/
 static void expand_cache3(struct city *pcity, int fields_to_use,
                          const struct tile_stats *const stats)
 {
   int i;
+  struct results_set *results = &cache3.results[fields_to_use];
+  struct results_set *prev_results = &cache3.results[fields_to_use-1];
 
   freelog(EXPAND_CACHE3_LOG_LEVEL,
          "expand_cache3(fields_to_use=%d) results[%d] "
@@ -1016,18 +1048,11 @@ static void expand_cache3(struct city *p
          fields_to_use, fields_to_use - 1,
          count_valid_combinations(fields_to_use - 1));
 
-  for (i = 0; i < MAX_COMBINATIONS; i++) {
-    cache3.results[fields_to_use].combinations[i].is_valid = FALSE;
-  }
+  assert(results->ncombinations==0);
 
-  for (i = 0; i < MAX_COMBINATIONS; i++) {
-    struct combination *current =
-       &cache3.results[fields_to_use - 1].combinations[i];
+  for (i = 0; i < prev_results->ncombinations; i++) {
+    struct combination *current = prev_results->combinations[i];
 
-    if (!current->is_valid) {
-      continue;
-    }
-
     my_city_map_iterate(pcity, x, y) {
       struct combination new_pc;
 
@@ -1052,17 +1077,10 @@ static void expand_cache3(struct city *p
          fields_to_use, count_valid_combinations(fields_to_use));
 
   if (SHOW_EXPAND_CACHE3_RESULT) {
-    for (i = 0; i < MAX_COMBINATIONS; i++) {
-      struct combination *current =
-         &cache3.results[fields_to_use].combinations[i];
-
-      if (!current->is_valid) {
-       continue;
+    for (i = 0; i < results->ncombinations; i++) {
+      print_combination(pcity, results->combinations[i]);
       }
-
-      print_combination(pcity, current);
     }
-  }
 }
 
 /****************************************************************************
@@ -1167,15 +1185,14 @@ static void build_cache3(struct city *pc
   cache3.pcity = pcity;
   stats.cache3.misses++;
 
-  /* Make as invalid the parts of cache3 we'll use. */
+  /* For speed, only clear the parts of cache3 we'll use */
   for (i = 0; i < MIN(pcity->size, MAX_FIELDS_USED) + 1; i++) {
-    for (j = 0; j < MAX_COMBINATIONS; j++) {
-      cache3.results[i].combinations[j].is_valid = FALSE;
-      if (cache3.results[i].combinations[j].cache1) {
-        free(cache3.results[i].combinations[j].cache1);
-        cache3.results[i].combinations[j].cache1 = NULL;
-      }
+    struct results_set *results = &cache3.results[i];
+    for (j = 0; j < results->ncombinations; j++) {
+      free_combination(results->combinations[j]);
+      results->combinations[j] = NULL;
     }
+    results->ncombinations = 0;
   }
 
   /*
@@ -1215,7 +1232,6 @@ static void build_cache3(struct city *pc
   } my_city_map_iterate_end;
 
   /* Add root combination. */
-  root_combination.is_valid = TRUE;
   add_combination(0, &root_combination);
 
   for (i = 1; i <= MIN(cache3.fields_available_total, pcity->size); i++) {
@@ -1338,19 +1354,15 @@ static void optimize_final(struct city *
   for (fields_used = 0;
        fields_used <= MIN(cache3.fields_available_total, pcity->size);
        fields_used++) {
+    struct results_set *results = &cache3.results[fields_used];
     freelog(OPTIMIZE_FINAL_LOG_LEVEL,
            "there are %d combinations which use %d fields",
            count_valid_combinations(fields_used), fields_used);
-    for (i = 0; i < MAX_COMBINATIONS; i++) {
-      struct combination *current =
-         &cache3.results[fields_used].combinations[i];
+    for (i = 0; i < results->ncombinations; i++) {
+      struct combination *current = results->combinations[i];
       int stat, major_fitness, minor_fitness;
       struct cm_result result;
 
-      if (!current->is_valid) {
-       continue;
-      }
-
       freelog(OPTIMIZE_FINAL_LOG_LEVEL2, "  trying combination %d", i);
 
       /* this will set the all_entertainer result */
@@ -1428,8 +1440,16 @@ static void optimize_final(struct city *
 *****************************************************************************/
 void cm_init(void)
 {
+  int i;
+
   cache3.pcity = NULL;
 
+  for(i=0; i<=MAX_FIELDS_USED; ++i) {
+    cache3.results[i].combinations = NULL;
+    cache3.results[i].ncombinations = 0;
+    cache3.results[i].ncombinations_allocated = 0;
+  }
+
   /* Reset the stats. */
   memset(&stats, 0, sizeof(stats));
   stats.wall_timer = new_timer(TIMER_USER, TIMER_ACTIVE);
@@ -1440,6 +1460,7 @@ void cm_init(void)
 *****************************************************************************/
 void cm_free(void)
 {
+  int i;
   free_timer(stats.wall_timer);
   stats.wall_timer = NULL;
 
@@ -1452,7 +1473,14 @@ void cm_free(void)
   cache2.allocated_size = 0;
   cache2.allocated_trade = 0;
   cache2.allocated_luxury = 0;
+
   clear_cache();
+  for(i=0; i<=MAX_FIELDS_USED; ++i) {
+    free(cache3.results[i].combinations);
+    cache3.results[i].combinations = NULL;
+    cache3.results[i].ncombinations = 0;
+    cache3.results[i].ncombinations_allocated = 0;
+  }
 }
 
 /****************************************************************************

[Prev in Thread] Current Thread [Next in Thread]