docs: @Override
    protected void preprocess() {
      final Logging log = getLogger(); // Could be subclass
      createStorage();
  
      ArrayDBIDs ids = DBIDUtil.ensureArray(relation.getDBIDs());
  
      if(log.isStatistics()) {
        log.statistics(new LongStatistic(this.getClass().getName() + ".k", k));
      }
      Duration duration = log.isStatistics() ? log.newDuration(this.getClass().getName() + ".precomputation-time").begin() : null;
      FiniteProgress progress = getLogger().isVerbose() ? new FiniteProgress("Materializing k nearest neighbors (k=" + k + ")", ids.size(), getLogger()) : null;
      // Try bulk
      List<? extends KNNList> kNNList = null;
      if(usebulk) {
        kNNList = knnQuery.getKNNForBulkDBIDs(ids, k);
        if(kNNList != null) {
          int i = 0;
          for(DBIDIter id = ids.iter(); id.valid(); id.advance(), i++) {
            storage.put(id, kNNList.get(i));
            log.incrementProcessed(progress);
          }
        }
      }
      else {
        final boolean ismetric = getDistanceQuery().getDistanceFunction().isMetric();
        for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
          if(ismetric && storage.get(iter) != null) {
            log.incrementProcessed(progress);
            continue; // Previously computed (duplicate point?)
          }
          KNNList knn = knnQuery.getKNNForDBID(iter, k);
          storage.put(iter, knn);
          if(ismetric) {
            for(DoubleDBIDListIter it = knn.iter(); it.valid() && it.doubleValue() == 0.; it.advance()) {
              storage.put(it, knn); // Reuse
            }
          }
          log.incrementProcessed(progress);
        }
      }
      log.ensureCompleted(progress);
      if(duration != null) {
        log.statistics(duration.end());
      }
    }