Index: src/test/org/apache/lucene/search/TestScorerPerf.java
===================================================================
--- src/test/org/apache/lucene/search/TestScorerPerf.java	(revision 0)
+++ src/test/org/apache/lucene/search/TestScorerPerf.java	(revision 0)
@@ -0,0 +1,157 @@
+package org.apache.lucene.search;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+import java.util.BitSet;
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+
+/**
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author yonik
+ * @version $Id$
+ */
+public class TestScorerPerf extends TestCase {
+  Random r = new Random(0);
+  boolean validate = true;  // set to false when doing performance testing
+
+  BitSet[] sets;
+  IndexSearcher s;
+
+  public void createDummySearcher() throws Exception {
+      // Create a dummy index with nothing in it.
+    // This could possibly fail if Lucene starts checking for docid ranges...
+    RAMDirectory rd = new RAMDirectory();
+    IndexWriter iw = new IndexWriter(rd,new WhitespaceAnalyzer(), true);
+    iw.close();
+    s = new IndexSearcher(rd);
+  }
+
+  public BitSet randBitSet(int sz, int numBitsToSet) {
+    BitSet set = new BitSet(sz);
+    for (int i=0; i<numBitsToSet; i++) {
+      set.set(r.nextInt(sz));
+    }
+    return set;
+  }
+
+  public BitSet[] randBitSets(int numSets, int setSize) {
+    BitSet[] sets = new BitSet[numSets];
+    for (int i=0; i<sets.length; i++) {
+      sets[i] = randBitSet(setSize, r.nextInt(setSize));
+    }
+    return sets;
+  }
+
+  public static class BitSetFilter extends Filter {
+    public BitSet set;
+    public BitSetFilter(BitSet set) {
+      this.set = set;
+    }
+    public BitSet bits(IndexReader reader) throws IOException {
+      return set;
+    }
+  }
+
+  public static class CountingHitCollector extends HitCollector {
+    int count=0;
+    int sum=0;
+
+    public void collect(int doc, float score) {
+      count++;
+      sum += doc;  // use it to avoid any possibility of being optimized away
+    }
+
+    public int getCount() { return count; }
+    public int getSum() { return sum; }
+  }
+
+
+  public static class MatchingHitCollector extends CountingHitCollector {
+    BitSet answer;
+    int pos=-1;
+    public MatchingHitCollector(BitSet answer) {
+      this.answer = answer;
+    }
+
+    public void collect(int doc, float score) {
+      pos = answer.nextSetBit(pos+1);
+      if (pos != doc) {
+        throw new RuntimeException("Expected doc " + pos + " but got " + doc);
+      }
+      super.collect(doc,score);
+    }
+  }
+
+
+
+
+  public int doConjunctions(int iter, int maxClauses) throws IOException {
+    int ret=0;
+
+    for (int i=0; i<iter; i++) {
+      int nClauses = r.nextInt(maxClauses-1)+2; // min 2 clauses
+      BooleanQuery bq = new BooleanQuery();
+      BitSet result=null;
+      for (int j=0; j<nClauses; j++) {
+        BitSet rnd = sets[r.nextInt(sets.length)];
+        Query q = new ConstantScoreQuery(new BitSetFilter(rnd));
+        bq.add(q, BooleanClause.Occur.MUST);
+        if (validate) {
+          if (result==null) result = (BitSet)rnd.clone();
+          else result.and(rnd);
+        }
+      }
+
+      CountingHitCollector hc = validate ? new MatchingHitCollector(result)
+                                         : new CountingHitCollector();
+      s.search(bq, hc);
+      ret += hc.getSum();
+      if (validate) assertEquals(result.cardinality(), hc.getCount());
+      // System.out.println(hc.getCount());
+    }
+    
+    return ret;
+  }
+
+  public void testConjunctions() throws Exception {
+    // test many small sets... the bugs will be found on boundary conditions
+    createDummySearcher();
+    validate=true;
+    sets=randBitSets(1000,10);
+    doConjunctions(10000,5);
+    s.close();
+  }
+
+  public void testConjunctionPerf() throws Exception {
+    createDummySearcher();
+    validate=false;
+    sets=randBitSets(32,1000000);
+    long start = System.currentTimeMillis();
+    doConjunctions(2000,6);
+    long end = System.currentTimeMillis();
+    s.close();
+    System.out.println("milliseconds="+(end-start));
+  }
+
+}

Property changes on: src/test/org/apache/lucene/search/TestScorerPerf.java
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/search/BooleanScorer2.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanScorer2.java	(revision 465853)
+++ src/java/org/apache/lucene/search/BooleanScorer2.java	(working copy)
@@ -115,7 +115,7 @@
    * When "sum" is used in a name it means score value summing
    * over the matching scorers
    */
-  private void initCountingSumScorer() {
+  private void initCountingSumScorer() throws IOException {
     coordinator.init();
     countingSumScorer = makeCountingSumScorer();
   }
@@ -168,10 +168,10 @@
 
   private static Similarity defaultSimilarity = new DefaultSimilarity();
 
-  private Scorer countingConjunctionSumScorer(List requiredScorers) {
+  private Scorer countingConjunctionSumScorer(List requiredScorers) throws IOException {
     // each scorer from the list counted as a single matcher
     final int requiredNrMatchers = requiredScorers.size();
-    ConjunctionScorer cs = new ConjunctionScorer(defaultSimilarity) {
+    return new ConjunctionScorer(defaultSimilarity, requiredScorers) {
       private int lastScoredDoc = -1;
 
       public float score() throws IOException {
@@ -186,35 +186,26 @@
         return super.score();
       }
     };
-    Iterator rsi = requiredScorers.iterator();
-    while (rsi.hasNext()) {
-      cs.add((Scorer) rsi.next());
-    }
-    return cs;
   }
 
-  private Scorer dualConjunctionSumScorer(Scorer req1, Scorer req2) { // non counting. 
-    final int requiredNrMatchers = requiredScorers.size();
-    ConjunctionScorer cs = new ConjunctionScorer(defaultSimilarity);
+  private Scorer dualConjunctionSumScorer(Scorer req1, Scorer req2) throws IOException { // non counting.
+    return new ConjunctionScorer(defaultSimilarity, new Scorer[]{req1, req2});
     // All scorers match, so defaultSimilarity super.score() always has 1 as
     // the coordination factor.
     // Therefore the sum of the scores of two scorers
     // is used as score.
-    cs.add(req1);
-    cs.add(req2);
-    return cs;
   }
 
   /** Returns the scorer to be used for match counting and score summing.
    * Uses requiredScorers, optionalScorers and prohibitedScorers.
    */
-  private Scorer makeCountingSumScorer() { // each scorer counted as a single matcher
+  private Scorer makeCountingSumScorer() throws IOException { // each scorer counted as a single matcher
     return (requiredScorers.size() == 0)
           ? makeCountingSumScorerNoReq()
           : makeCountingSumScorerSomeReq();
   }
 
-  private Scorer makeCountingSumScorerNoReq() { // No required scorers
+  private Scorer makeCountingSumScorerNoReq() throws IOException { // No required scorers
     if (optionalScorers.size() == 0) {
       return new NonMatchingScorer(); // no clauses or only prohibited clauses
     } else { // No required scorers. At least one optional scorer.
@@ -235,7 +226,7 @@
     }
   }
 
-  private Scorer makeCountingSumScorerSomeReq() { // At least one required scorer.
+  private Scorer makeCountingSumScorerSomeReq() throws IOException { // At least one required scorer.
     if (optionalScorers.size() < minNrShouldMatch) {
       return new NonMatchingScorer(); // fewer optional clauses than minimum that should match
     } else if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required.
Index: src/java/org/apache/lucene/search/ConjunctionScorer.java
===================================================================
--- src/java/org/apache/lucene/search/ConjunctionScorer.java	(revision 465853)
+++ src/java/org/apache/lucene/search/ConjunctionScorer.java	(working copy)
@@ -17,120 +17,88 @@
  */
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Comparator;
+import java.util.Collection;
 
 /** Scorer for conjunctions, sets of queries, all of which are required. */
 class ConjunctionScorer extends Scorer {
-  private Scorer[] scorers = new Scorer[2];
-  private int length = 0;
-  private int first = 0;
-  private int last = -1;
-  private boolean firstTime = true;
-  private boolean more = true;
-  private float coord;
+  private final Scorer[] scorers;
 
-  public ConjunctionScorer(Similarity similarity) {
-    super(similarity);
+  // Current doc that each scorer is on.
+  // This is a workaround for the fact that doc() is not defined before
+  // next() or skipTo() is called.  But sometimes we don't want to call
+  // skipTo if the scorer is already past the target.  The other
+  // way around this would be to initialize all the scorers by calling
+  // next() on all of them in the constructor... then one could rely
+  // on scorer[i].doc() for the current position.
+  private final int[] docs;
+
+  int maxDoc =-1;  // maximum document seen so far
+  private boolean more;
+  private final float coord;
+
+  public ConjunctionScorer(Similarity similarity, Collection scorers) throws IOException {
+    this(similarity, (Scorer[])scorers.toArray(new Scorer[scorers.size()]));
   }
 
-  final void add(Scorer scorer) {
-    if (length >= scorers.length) {
-      // grow the array
-      Scorer[] temps = new Scorer[scorers.length * 2];
-      System.arraycopy(scorers, 0, temps, 0, length);
-      scorers = temps;
+  public ConjunctionScorer(Similarity similarity, Scorer[] scorers) throws IOException {
+    super(similarity);
+    this.scorers = scorers;
+    docs = new int[scorers.length];
+    // Arrays.fill(docs,-1);
+    // calling next() for each scorer could be removed if skipTo() worked
+    // on all scorers w/o first calling next()
+    for (int i=0; i<scorers.length; i++) {
+      more = scorers[i].next();
+      if (!more) break;
+      docs[i]=scorers[i].doc();
+      // maxDoc=Math.max(maxDoc,docs[i]);
     }
-    last += 1;
-    length += 1;
-    scorers[last] = scorer;
+    coord = getSimilarity().coord(this.scorers.length, this.scorers.length);
   }
 
-  public int doc() { return scorers[first].doc(); }
+  public int doc() { return maxDoc; }
 
   public boolean next() throws IOException {
-    if (firstTime) {
-      init(true);
-    } else if (more) {
-      more = scorers[last].next();                   // trigger further scanning
-    }
-    return doNext();
+    return skipTo(maxDoc+1);
   }
-  
-  private boolean doNext() throws IOException {
-    while (more && scorers[first].doc() < scorers[last].doc()) { // find doc w/ all clauses
-      more = scorers[first].skipTo(scorers[last].doc());      // skip first upto last
-      last = first; // move first to last
-      first = (first == length-1) ? 0 : first+1;
-    }
-    return more;                                // found a doc with all clauses
-  }
 
   public boolean skipTo(int target) throws IOException {
-    if(firstTime) {
-      init(false);
+    // The "more" state variable could possibly be removed if calling next() or skipTo()
+    // on a scorer that returned false is illegal.
+    // This could be changed to an exception for testing.
+    if (!more) return false;
+
+    // if we have already encountered a higher doc, start there.
+    maxDoc = Math.max(target, maxDoc);
+
+    int numMatches=0;
+    int pos=0;
+    for(;;) {
+      if (docs[pos]<maxDoc) {
+        Scorer scorer = scorers[pos];        
+        more = scorer.skipTo(maxDoc);
+        if (!more) return false;  // if any scorer runs out, we are done
+        docs[pos] = scorer.doc();
+      }
+      maxDoc = docs[pos];
+      if (maxDoc == target) {
+        if (++numMatches>=scorers.length) return true;  // all the scorers matched maxDoc
+      } else {
+        target = maxDoc;  // adjust target to higher doc encountered
+        numMatches=1;     // reset count to 1 (match current doc)
+      }
+      if (++pos==scorers.length) pos=0;
     }
-    
-    for (int i = 0, pos = first; i < length; i++) {
-      if (!more) break; 
-      more = scorers[pos].skipTo(target);
-      pos = (pos == length-1) ? 0 : pos+1;
-    }
-    
-    if (more)
-      sortScorers();                              // re-sort scorers
-    
-    return doNext();
   }
 
   public float score() throws IOException {
     float sum = 0.0f;
-    for (int i = 0; i < length; i++) {
+    for (int i = 0; i < scorers.length; i++) {
       sum += scorers[i].score();
     }
     return sum * coord;
   }
-  
-  private void init(boolean initScorers) throws IOException {
-    //  compute coord factor
-    coord = getSimilarity().coord(length, length);
-   
-    more = length > 0;
 
-    if(initScorers){
-      // move each scorer to its first entry
-      for (int i = 0, pos = first; i < length; i++) {
-        if (!more) break; 
-        more = scorers[pos].next();
-        pos = (pos == length-1) ? 0 : pos+1;
-      }
-      // initial sort of simulated list
-      if (more) 
-        sortScorers();
-    }
-
-    firstTime = false;
-  }
-
-  private void sortScorers() {
-    // squeeze the array down for the sort
-    if (length != scorers.length) {
-      Scorer[] temps = new Scorer[length];
-      System.arraycopy(scorers, 0, temps, 0, length);
-      scorers = temps;
-    }
-    
-    // note that this comparator is not consistent with equals!
-    Arrays.sort(scorers, new Comparator() {         // sort the array
-        public int compare(Object o1, Object o2) {
-          return ((Scorer)o1).doc() - ((Scorer)o2).doc();
-        }
-      });
-   
-    first = 0;
-    last = length - 1;
-  }
-
   public Explanation explain(int doc) {
     throw new UnsupportedOperationException();
   }
Index: src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanQuery.java	(revision 465853)
+++ src/java/org/apache/lucene/search/BooleanQuery.java	(working copy)
@@ -236,16 +236,15 @@
       }
 
       if (allRequired && noneBoolean) {           // ConjunctionScorer is okay
-        ConjunctionScorer result =
-          new ConjunctionScorer(similarity);
+        Scorer[] scorers = new Scorer[weights.size()];
         for (int i = 0 ; i < weights.size(); i++) {
           Weight w = (Weight)weights.elementAt(i);
           Scorer subScorer = w.scorer(reader);
           if (subScorer == null)
             return null;
-          result.add(subScorer);
+          scorers[i] = subScorer;
         }
-        return result;
+        return new ConjunctionScorer(similarity, scorers);
       }
 
       // Use good-old BooleanScorer instead.
