diff --git hierarchical_select.module hierarchical_select.module
index 17a713f..ac0e660 100755
--- hierarchical_select.module
+++ hierarchical_select.module
@@ -1663,9 +1663,19 @@ function _hierarchical_select_hierarchy_generate($config, $selection, $required,
       }
     }
     // Reconstruct all sublevels.
+    // Try high-performance hook first.
+    $multi_hook = 'hierarchical_select_children_multiple';
+    if (function_exists($config['module'] . '_' . $multi_hook)) {
+      $children_multiple = module_invoke($config['module'], $multi_hook, $selection, $config['params']);
+    }
     for ($i = 0; $i < count($selection); $i++) {
-      $children = array_keys(module_invoke($config['module'], 'hierarchical_select_children', $selection[$i], $config['params']));
-
+      if (isset($children_multiple) && isset($children_multiple[$selection[$i]])) {
+        $children = array_keys($children_multiple[$selection[$i]]);
+      }
+      else {
+        // Fallback to the old hook.
+        $children = array_keys(module_invoke($config['module'], 'hierarchical_select_children', $selection[$i], $config['params']));
+      }
       // Ensure the next item in the selection is a child of the current item.
       for ($j = $i + 1; $j < count($selection); $j++) {
         if (in_array($selection[$j], $children)) {
@@ -1677,7 +1687,7 @@ function _hierarchical_select_hierarchy_generate($config, $selection, $required,
 
   // Validate the hierarchy.
   $selection = _hierarchical_select_hierarchy_validate($selection, $config['module'], $config['params']);
-
+  
   // When nothing is currently selected, set the root level to:
   // - "<none>" (or its equivalent special item) when:
   //    - enforce_deepest is enabled *and* level labels are enabled *and*
@@ -1988,11 +1998,35 @@ function _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config) {
   foreach ($hierarchy->levels as $depth => $level) {
     foreach (array_keys($level) as $item) {
       if (!preg_match('/(none|label_\d+|create_new_item)/', $item)) {
-        $hierarchy->childinfo[$depth][$item] = count(module_invoke($config['module'], 'hierarchical_select_children', $item, $config['params']));
+        $depth_items[$depth][] = $items[] = $item;
       }
     }
   }
+  // Terminate if we have nothing to do.
+  if (empty($items)) {
+    return $hierarchy;
+  }
+  
+  // Count children for _all_ items in the hierarchy.
+  // Use high-performance hook if possible.
+  $hook = 'hierarchical_select_children_count';
+  if (function_exists($config['module'] . '_' . $hook)) {
+    $counts = module_invoke($config['module'], $hook, $items, $config['params']);
+  }
+  else {
+    // Fallback to the old hook.
+    foreach($items as $item) {
+      $counts[$item] = count(module_invoke($config['module'], 'hierarchical_select_children', $item, $config['params']));
+    }
+  }
 
+  // Update hierarchy.
+  foreach ($hierarchy->levels as $depth => $level) {
+    foreach ($depth_items[$depth] as $item) {
+      $hierarchy->childinfo[$depth][$item] = $counts[$item];   
+    }
+  }
+  
   return $hierarchy;
 }
 
diff --git modules/hs_content_taxonomy.module modules/hs_content_taxonomy.module
index 486e60c..126c353 100755
--- modules/hs_content_taxonomy.module
+++ modules/hs_content_taxonomy.module
@@ -386,20 +386,100 @@ function hs_content_taxonomy_hierarchical_select_children($parent, $params) {
   $tid = $params['tid'];
   $depth = $params['depth'];
 
-  // Keep a static cache of the entire tree, this allows us to quickly look up
-  // if a term is not too deep – because if it's too deep, we don't want to
-  // return any children.
-  if (!isset($tree[$vid][$tid])) {
-    $raw_tree = _hs_taxonomy_hierarchical_select_get_tree($vid, $tid);
-    foreach ($raw_tree as $term) {
-      $tree[$vid][$tid][$term->tid] = $term->depth;
+  // If depth is unlimited every level is valid.
+  if ($depth == 0) {
+    $terms = _hs_taxonomy_hierarchical_select_get_tree($vid, $parent, -1, 1);
+  }
+  else {
+    // Otherwise, return only terms not deeper than $depth.
+    // $depth > 0 should be really considered as a very low performance option 
+    // and should be avoided when possible.
+  
+    // Keep a static cache of the entire tree, this allows us to quickly look up
+    // if a term is not too deep – because if it's too deep, we don't want to
+    // return any children.
+    if (!isset($tree[$vid][$tid])) {
+      $raw_tree = _hs_taxonomy_hierarchical_select_get_tree($vid, $tid);
+      foreach ($raw_tree as $term) {
+        $tree[$vid][$tid][$term->tid] = $term->depth;
+      }
+    }
+    if ($tree[$vid][$tid][$parent] + 1 >= $depth) {
+      $terms = array();  
+    }
+    else {
+      $terms = _hs_taxonomy_hierarchical_select_get_tree($vid, $parent, -1, 1);
     }
   }
-  $terms = ($depth > 0 && $tree[$vid][$tid][$parent] + 1 >= $depth) ? array() : _hs_taxonomy_hierarchical_select_get_tree($vid, $parent, -1, 1);
+  
   return _hs_taxonomy_hierarchical_select_terms_to_options($terms);
 }
 
 /**
+ * Implementation of hook_hierarchical_select_children_multiple().
+ */
+function hs_content_taxonomy_hierarchical_select_children_multiple($parents, $params) {
+  $vid = $params['vid'];
+  $tid = $params['tid'];
+  $depth = $params['depth'];
+  $result = array();
+
+  // If depth is unlimited every level is valid.
+  if ($depth == 0) {
+    $terms = _hs_taxonomy_hierarchical_select_get_tree($vid, $parents, -1, 1);
+    
+    // Group up terms by their parents.
+    foreach ($terms as $term) {
+      // A term can have multiple parents and be present in $result multiple times.
+      foreach ($parents as $parent) { 
+        if (in_array($parent, $term->parents)) {
+          $result[$parent][] = $term;
+        }
+      }
+    }
+    foreach ($result as $parent => $children_terms) {
+      $result[$parent] = _hs_taxonomy_hierarchical_select_terms_to_options($children_terms);
+    }
+  }
+  else {
+    // Fallback to the low-performance function.
+    foreach($parents as $parent) {
+      $result[$parent] = hs_content_taxonomy_hierarchical_select_children($parent, $params);
+    }
+  }
+  
+  return $result;
+}
+
+/**
+ * Implementation of hook_hierarchical_select_children_count().
+ */
+function hs_content_taxonomy_hierarchical_select_children_count($parents, $params) {
+  $vid = $params['vid'];
+  $tid = $params['tid'];
+  $depth = $params['depth'];
+  
+  // If depth is unlimited every level is valid.
+  if ($depth == 0) {
+    $query  = 'SELECT t.tid, COUNT(h.tid) AS children FROM {term_data} t LEFT JOIN {term_hierarchy} h ON t.tid = h.parent';
+    $query .= ' WHERE t.vid = %d AND t.tid IN (' . db_placeholders($parents) . ')';
+    $query .= ' GROUP BY t.tid';
+    $result = db_query(db_rewrite_sql($query, 't', 'tid'), array_merge(array($vid), $parents));   
+    while ($row = db_fetch_object($result)) {
+      $counts[$row->tid] = $row->children;
+    }
+  }
+  else {
+    // Fallback to the low-performance way: select and count children.
+    foreach($parents as $parent) {
+      $counts[$parent] = count(hs_content_taxonomy_hierarchical_select_children($parent, $params));
+    }
+  }
+  
+  return $counts;
+}
+
+/**
  * Implementation of hook_hierarchical_select_lineage().
  */
 function hs_content_taxonomy_hierarchical_select_lineage($item, $params) {
diff --git modules/hs_taxonomy.module modules/hs_taxonomy.module
index 99fe0d1..420a15e 100755
--- modules/hs_taxonomy.module
+++ modules/hs_taxonomy.module
@@ -369,7 +369,7 @@ function hs_taxonomy_hierarchical_select_children($parent, $params) {
     return array();
   }
 
-  $terms = taxonomy_get_children($parent, $params['vid']);
+  $terms = _hs_taxonomy_hierarchical_select_get_tree($params['vid'], $parent, -1, 1);
 
   // Unset the term that's being excluded, if it is among the children.
   unset($terms[$params['exclude_tid']]);
@@ -388,6 +388,70 @@ function hs_taxonomy_hierarchical_select_children($parent, $params) {
 }
 
 /**
+ * Implementation of hook_hierarchical_select_children_multiple().
+ */
+function hs_taxonomy_hierarchical_select_children_multiple($parents, $params) {
+  $vid = $params['vid'];
+  $result = array();
+
+  // If Term Permissions module is missing, all terms are valid.
+  // TODO: can Term Permissions module do it's job using db_rewrite_sql() ?
+  if (!function_exists('term_permissions_allowed')) {
+    $terms = _hs_taxonomy_hierarchical_select_get_tree($vid, $parents, -1, 1);
+    
+    // Group up terms by their parents.
+    foreach ($terms as $term) {
+      // A term can have multiple parents and be present in $result multiple times.
+      foreach ($parents as $parent) { 
+        if (in_array($parent, $term->parents)) {
+          $result[$parent][] = $term;
+        }
+      }
+    }
+    foreach ($result as $parent => $children_terms) {
+      $result[$parent] = _hs_taxonomy_hierarchical_select_terms_to_options($children_terms);
+    }
+  }
+  else {
+    // Fallback to the low-performance function.
+    foreach($parents as $parent) {
+      $result[$parent] = hs_taxonomy_hierarchical_select_children($parent, $params);
+    }
+  }
+  
+  return $result;
+}
+
+
+/**
+ * Implementation of hook_hierarchical_select_children_count().
+ */
+function hs_taxonomy_hierarchical_select_children_count($parents, $params) {
+  $vid = $params['vid'];
+  
+  // If Term Permissions module is missing, count using DB.
+  // TODO: can Term Permissions module do it's job using db_rewrite_sql() ?
+  if (!function_exists('term_permissions_allowed')) {
+    $query  = 'SELECT t.tid, COUNT(h.tid) AS children FROM {term_data} t LEFT JOIN {term_hierarchy} h ON t.tid = h.parent';
+    $query .= ' WHERE t.vid = %d AND t.tid IN (' . db_placeholders($parents) . ')';
+    $query .= ' GROUP BY t.tid';
+    $result = db_query(db_rewrite_sql($query, 't', 'tid'), array_merge(array($vid), $parents));   
+    while ($row = db_fetch_object($result)) {
+      $counts[$row->tid] = $row->children;
+    }
+  }
+  else {
+    // Term Permissions module is present. We are stuck counting old 
+    // low-performance way: select and count children.
+    foreach($parents as $parent) {
+      $counts[$parent] = count(hs_taxonomy_hierarchical_select_children($parent, $params));
+    }
+  }
+  
+  return $counts;
+}
+
+/**
  * Implementation of hook_hierarchical_select_lineage().
  */
 function hs_taxonomy_hierarchical_select_lineage($item, $params) {
@@ -729,45 +793,77 @@ function hierarchical_select_taxonomy_form_vocabulary_submit($form, &$form_state
  * two calls to it.
  */
 function _hs_taxonomy_hierarchical_select_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL, $reset = FALSE) {
-  static $children, $parents, $terms;
-
+  static $children, $parents, $terms; 
   if ($reset) {
     $children = $parents = $terms = array();
   }
+  $tree = array();
+    
+  // Transform parent to array. Instead of single parent we pass multiple ones
+  // to the next step so that we can process them together in a single query.
+  if (!is_array($parent)) {
+    $parent = array($parent);
+  }
 
+  // Because we load tree step by step we can't know max number of children 
+  // so we set it to a safe unreachable limit in case it's not provided.
+  $max_depth = (is_null($max_depth)) ? 99999999 : $max_depth;
   $depth++;
-
-  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
-  // and its children, too.
-  if (!isset($children[$vid])) {
-    $children[$vid] = array();
-
-    $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN  {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
+  
+  // Do we need current step ?
+  if ($max_depth <= $depth) {
+    return $tree;
+  }
+
+  // Prepare queue for the "IN ( .. )" part of query.
+  $queue = array();
+  foreach ($parent as $single_parent) {
+    // Queue branch for processing if it's not cached yet.
+    if (!isset($children[$vid][$single_parent])) {
+      $queue[] = $single_parent;
+      // Use an empty array to distinguish between a stub (without children)
+      // term and a branch that is not loaded yet.
+      $children[$vid][$single_parent] = array();
+    }
+  }  
+  // Run the query.
+  if (!empty($queue)) {
+    $query  = 'SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid';
+    $query .= ' WHERE t.vid = %d AND parent IN (' . db_placeholders($queue) . ')';
+    // We may or may not need this:
+    //$query .= ' ORDER BY weight, name';
+    $result = db_query(db_rewrite_sql($query, 't', 'tid'), array_merge(array($vid), $queue));   
     while ($term = db_fetch_object($result)) {
       $children[$vid][$term->parent][] = $term->tid;
       $parents[$vid][$term->tid][] = $term->parent;
       $terms[$vid][$term->tid] = $term;
     }
   }
-
-  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
-  if (isset($children[$vid][$parent])) {
-    foreach ($children[$vid][$parent] as $child) {
-      if ($max_depth > $depth) {
-        $term = drupal_clone($terms[$vid][$child]);
-        $term->depth = $depth;
-        // The "parent" attribute is not useful, as it would show one parent only.
-        unset($term->parent);
-        $term->parents = $parents[$vid][$child];
-        $tree[] = $term;
-
-        if (isset($children[$vid][$child])) {
-          $tree = array_merge($tree, _hs_taxonomy_hierarchical_select_get_tree($vid, $child, $depth, $max_depth));
+  
+  $next_parent = array();
+  foreach ($parent as $single_parent) {    
+    foreach ($children[$vid][$single_parent] as $child) {
+      $term = drupal_clone($terms[$vid][$child]);
+      $term->depth = $depth;
+      // The "parent" attribute is not useful, as it would show one parent only.
+      unset($term->parent);
+      $term->parents = $parents[$vid][$child];
+      $tree[] = $term;
+
+      // Need more steps ?
+      if ($max_depth > $depth + 1) {
+        // Queue children for the next step down the tree. Do not process 
+        // children which we already know as stub ones.
+        if (!isset($children[$vid][$child]) || !empty($children[$vid][$child])) {
+          $next_parent[] = $child;
         }
       }
     }
+  }  
+  if (!empty($next_parent)) {
+    // Process multiple children together i.e. next level.
+    $tree = array_merge($tree, _hs_taxonomy_hierarchical_select_get_tree($vid, $next_parent, $depth, $max_depth));
   }
-
   return isset($tree) ? $tree : array();
 }
 
