ROOT logo
//*********************************************************************
//+ Evaluate the performance of THnSparse vs THnF (or Float_t arrays)
//  for different numbers of dimensions and bins per dimension.
// 
//  The script calculates the bandwidth for filling and retrieving
//  bin contents (in million entries per second) for these two
//  histogramming techniques, where "seconds" is CPU and real time.
//
//  The first line of the plots contains the bandwidth based on the 
//  CPU time (THnSpase, THnF/Float_t*, ratio), the second line shows
//  the plots for real time, and the third line shows the fraction of
//  filled bins and memory used by THnSparse vs. THnF/Float_t.
// 
//  The timing depends on the distribution and the amount of entries
//  in the histograms; here, a Gaussian distribution (center is
//  contained in the histograms) is used to fill each histogram with
//  1000 entries. The filling and reading is repeated until enough
//  statistics have been collected.
// 
//  tutorials/tree/drawsparse.C shows an example for visualizing a
//  THnSparse. It creates a TTree which is then drawn using
//  TParallelCoord.
// 
//  This macro should be run in compiled mode due to the many nested
//  loops that force CINT to disable its optimization. If run
//  interpreted one would not benchmark THnSparse but CINT.
// 
//  Run as
//    .L $ROOTSYS/tutorials/hist/sparsehist.C+
// 
//  Axel.Naumann@cern.ch (2007-09-14)
// *********************************************************************

#include "TH1.h"
#include "TH2.h"
#include "TH3.h"
#include "THnSparse.h"
#include "TStopwatch.h"
#include "TRandom.h"
#include "TCanvas.h"
#include "TFile.h"
#include "TStyle.h"
#include "TSystem.h"

class TTimeHists {
public:
   enum EHist { kHist, kSparse, kNumHist };
   enum ETime { kReal, kCPU, kNumTime };
   TTimeHists(Int_t dim, Int_t bins, Long_t num):
      fValue(0), fDim(dim), fBins(bins), fNum(num),
      fSparse(0), fHist(0), fArray(0) {}
   ~TTimeHists();
   bool Run();
   Double_t GetTime(EHist hist, ETime time) const {
      if (time == kReal) return fTime[hist][0];
      return fTime[hist][1]; }
   static void SetDebug(Int_t lvl) { fgDebug = lvl; }
   THnSparse* GetSparse() const { return fSparse; }

protected:
   void Fill(EHist hist);
   Double_t Check(EHist hist);
   void SetupHist(EHist hist);
   void NextValues();
   void SetupValues();

private:
   Double_t* fValue;
   Int_t fDim;
   Int_t fBins;
   Long_t fNum;
   Double_t fTime[2][2];
   THnSparse* fSparse;
   TH1*       fHist;
   Float_t*  fArray;
   static Int_t fgDebug;
};

Int_t TTimeHists::fgDebug = 0;

TTimeHists::~TTimeHists()
{
   delete [] fValue;
   delete fSparse;
   delete fHist;
   delete [] fArray;
}

bool TTimeHists::Run()
{
   // run all tests with current settings, and check for identity of content.

   Double_t check[2];
   Long64_t rep[2];
   for (int h = 0; h < 2; ++h) {
      rep[h] = 0;
      SetupValues();
      try {
         TStopwatch w;
         w.Start();
         SetupHist((EHist) h);
         w.Stop();
         do {
            w.Start(kFALSE);
            Fill((EHist) h);
            check[h] = Check((EHist) h);
            w.Stop();
            ++rep[h];
         } while (!h && w.RealTime() < 0.1
            || h && rep[0] > 0 && rep[1] < rep[0]);

         fTime[h][0] = (1.* fNum * rep[h]) / w.RealTime() / 1E6;
         fTime[h][1] = (1.* fNum * rep[h]) / w.CpuTime() / 1E6;

         if (h == 1 && (fTime[h][0] > 1E20 || fTime[h][1] > 1E20)) {
            do {
               // some more cycles:
               w.Start(kFALSE);
               Fill((EHist) h);
               Check((EHist) h);
               w.Stop();
               ++rep[h];
            } while (w.RealTime() < 0.1);

            fTime[h][0] = (1.* fNum * rep[h]) / w.RealTime() / 1E6;
            fTime[h][1] = (1.* fNum * rep[h]) / w.CpuTime() / 1E6;
         }

         if (fTime[h][0] > 1E20) fTime[h][0] = 1E20;
         if (fTime[h][1] > 1E20) fTime[h][1] = 1E20;
      }
      catch (std::exception&) {
         fTime[h][0] = fTime[h][1] = -1.;
         check[h] = -1.; // can never be < 1 without exception
         rep[h] = -1;
      }
   }
   if (check[0] != check[1])
      if (check[0] != -1.)
         printf("ERROR: mismatch of histogram / array (%g) and sparse histogram (%g) for dim=%d, bins=%d!\n",
                check[0], check[1], fDim, fBins);
      // else
      //   printf("ERROR: cannot allocate histogram / array for dim=%d, bins=%d - out of memory!\n",
      //          fDim, fBins);
   return (check[0] == check[1]);
}

void TTimeHists::NextValues()
{
   for (Int_t d = 0; d < fDim; ++d)
      fValue[d] = gRandom->Gaus() / 4.;
}

void TTimeHists::SetupValues()
{
   // define fValue
   if (!fValue) fValue = new Double_t[fDim];
   gRandom->SetSeed(42);
}

void TTimeHists::Fill(EHist hist)
{
   for (Long_t n = 0; n < fNum; ++n) {
      NextValues();
      if (fgDebug > 1) {
         printf("%ld: fill %s", n, hist == kHist? (fDim < 4 ? "hist" : "arr") : "sparse");
         for (Int_t d = 0; d < fDim; ++d)
            printf("[%g]", fValue[d]);
         printf("\n");
      }
      if (hist == kHist) {
         switch (fDim) {
         case 1: fHist->Fill(fValue[0]); break;
         case 2: ((TH2F*)fHist)->Fill(fValue[0], fValue[1]); break;
         case 3: ((TH3F*)fHist)->Fill(fValue[0], fValue[1], fValue[2]); break;
         default:
            {
               Long_t pos = 0;
               for (Int_t d = 0; d < fDim; ++d) {
                  pos *= (fBins + 2);
                  Int_t ibin = (Int_t)((fValue[d] + 1.) / 2. * fBins + 1);
                  if (ibin > fBins) ibin = fBins + 1;
                  if (ibin < 0) ibin = 0;
                  pos += ibin;
               }
               ++fArray[pos];
            }
         }
      } else {
         fSparse->Fill(fValue);
      }
   }
}

void TTimeHists::SetupHist(EHist hist)
{
   if (hist == kHist) {
      switch (fDim) {
      case 1: fHist = new TH1F("h1", "h1", fBins, -1., 1.); break;
      case 2: fHist = new TH2F("h2", "h2", fBins, -1., 1., fBins, -1., 1.); break;
      case 3: fHist = new TH3F("h3", "h3", fBins, -1., 1., fBins, -1., 1., fBins, -1., 1.); break;
      default:
         {
            MemInfo_t meminfo;
            gSystem->GetMemInfo(&meminfo);
            Int_t size = 1;
            for (Int_t d = 0; d < fDim; ++d) {
               if ((Int_t)(size * sizeof(Float_t)) > INT_MAX / (fBins + 2)
                  || meminfo.fMemFree > 0 
                  && meminfo.fMemFree / 2 < (Int_t) (size * sizeof(Float_t)/1000/1000))
                  throw std::bad_alloc();
               size *= (fBins + 2);
            }
            if (meminfo.fMemFree > 0 
               && meminfo.fMemFree / 2 < (Int_t) (size * sizeof(Float_t)/1000/1000))
               throw std::bad_alloc();
            fArray = new Float_t[size];
            memset(fArray, 0, sizeof(Float_t)*size);
         }
      }
   } else {
      Int_t* bins = new Int_t[fDim];
      Double_t *xmin = new Double_t[fDim];
      Double_t *xmax = new Double_t[fDim];
      for (Int_t d = 0; d < fDim; ++d) {
         bins[d] = fBins;
         xmin[d] = -1.;
         xmax[d] =  1.;
      }
      fSparse = new THnSparseF("hs", "hs", fDim, bins, xmin, xmax);
   }
}

Double_t TTimeHists::Check(EHist hist)
{
   // Check bin content of all bins
   Double_t check = 0.;
   Int_t* x = new Int_t[fDim];
   memset(x, 0, sizeof(Int_t) * fDim);

   if (hist == kHist) {
      Long_t idx = 0;
      Long_t size = 1;
      for (Int_t d = 0; d < fDim; ++d)
         size *= (fBins + 2);
      while (x[0] <= fBins + 1) {
         Double_t v = -1.;
         if (fDim < 4) {
            Long_t histidx = x[0];
            if (fDim == 2) histidx = fHist->GetBin(x[0], x[1]);
            else if (fDim == 3) histidx = fHist->GetBin(x[0], x[1], x[2]);
            v = fHist->GetBinContent(histidx);
         }
         else v = fArray[idx];
         Double_t checkx = 0.;
         if (v)
            for (Int_t d = 0; d < fDim; ++d)
               checkx += x[d];
         check += checkx * v;

         if (fgDebug > 2 || fgDebug > 1 && v) {
            printf("%s%d", fDim < 4 ? "hist" : "arr", fDim);
            for (Int_t d = 0; d < fDim; ++d)
               printf("[%d]", x[d]);
            printf(" = %g\n", v);
         }

         ++x[fDim - 1];
         // Adjust the bin idx
         // no wrapping for dim 0 - it's what we break on!
         for (Int_t d = fDim - 1; d > 0; --d) {
            if (x[d] > fBins + 1) {
               x[d] = 0;
               ++x[d - 1];
            }
         }
         ++idx;
      } // while next bin
   } else {
      for (Long64_t i = 0; i < fSparse->GetNbins(); ++i) {
         Double_t v = fSparse->GetBinContent(i, x);
         Double_t checkx = 0.;
         for (Int_t d = 0; d < fDim; ++d)
            checkx += x[d];
         check += checkx * v;

         if (fgDebug > 1) {
            printf("sparse%d", fDim);
            for (Int_t d = 0; d < fDim; ++d)
               printf("[%d]", x[d]);
            printf(" = %g\n", v);
         }
      }
   }
   check /= fNum;
   if (fgDebug > 0)
      printf("check %s%d = %g\n", hist == kHist ? (fDim < 4 ? "hist" : "arr") : "sparse", fDim, check);
   return check;
}


void sparsehist() {
#ifdef __CINT__
   printf("Please run this script in compiled mode by running \".x sparsehist.C+\"\n");
   return;
#endif

   TH2F* htime[TTimeHists::kNumHist][TTimeHists::kNumTime];
   for (int h = 0; h < TTimeHists::kNumHist; ++h)
      for (int t = 0; t < TTimeHists::kNumTime; ++t) {
         TString name("htime_");
         if (h == 0) name += "arr";
         else name += "sp";
         if (t == 0) name += "_r";

         TString title;
         title.Form("Throughput (fill,get) %s (%s, 1M entries/sec);dim;bins;1M entries/sec", h == 0 ? "THnF/Float_t[n]" : "THnSparseF", t == 0 ? "real" : "CPU");
         htime[h][t] = new TH2F(name, title, 6, 0.5, 6.5, 10, 5, 105);
      }

   TH2F* hsparse_mem = new TH2F("hsparse_mem", "Fractional memory usage;dim;bins;fraction of memory used", 6, 0.5, 6.5, 10, 5, 105);
   TH2F* hsparse_bins = new TH2F("hsparse_bins", "Fractional number of used bins;dim;bins;fraction of filled bins", 6, 0.5, 6.5, 10, 5, 105);

   // TTimeHists::SetDebug(2);
   Double_t max = -1.;
   for (Int_t dim = 1; dim < 7; ++dim) {
      printf("Processing dimension %d", dim);
      for (Int_t bins = 10; bins <= 100; bins += 10) {
         TTimeHists timer(dim, bins, /*num*/ 1000);
         timer.Run();
         for (int h = 0; h < TTimeHists::kNumHist; ++h)
            for (int t = 0; t < TTimeHists::kNumTime; ++t) {
               Double_t time = timer.GetTime((TTimeHists::EHist)h, (TTimeHists::ETime)t);
               if (time >= 0.)
                  htime[h][t]->Fill(dim, bins, time);
            }

         hsparse_mem->Fill(dim, bins, timer.GetSparse()->GetSparseFractionMem());
         hsparse_bins->Fill(dim, bins, timer.GetSparse()->GetSparseFractionBins());

         if (max < timer.GetTime(TTimeHists::kSparse, TTimeHists::kReal))
            max = timer.GetTime(TTimeHists::kSparse, TTimeHists::kReal);
         printf(".");
         fflush(stdout);
      }
      printf(" done\n");
   }

   Double_t markersize = 2.5;
   hsparse_mem->SetMarkerSize(markersize);
   hsparse_bins->SetMarkerSize(markersize);

   TH2F* htime_ratio[TTimeHists::kNumTime];
   for (int t = 0; t < TTimeHists::kNumTime; ++t) {
      const char* name = t ? "htime_ratio" : "htime_ratio_r";
      htime_ratio[t] = (TH2F*) htime[TTimeHists::kSparse][t]->Clone(name);
      TString title;
      title.Form("Relative speed improvement (%s, 1M entries/sec): sparse/hist;dim;bins;#Delta 1M entries/sec", t == 0 ? "real" : "CPU");
      htime_ratio[t]->SetTitle(title);
      htime_ratio[t]->Divide(htime[TTimeHists::kHist][t]);
      htime_ratio[t]->SetMinimum(0.1);
      htime_ratio[t]->SetMarkerSize(markersize);
   }

   TFile* f = new TFile("sparsehist.root","RECREATE");

   TCanvas* canv= new TCanvas("c","c");
   canv->Divide(3,3);

   gStyle->SetPalette(8,0);
   gStyle->SetPaintTextFormat(".2g");
   gStyle->SetOptStat(0);
   const char* opt = "TEXT COL";

   for (int t = 0; t < TTimeHists::kNumTime; ++t) {
      for (int h = 0; h < TTimeHists::kNumHist; ++h) {
         htime[h][t]->SetMaximum(max);
         htime[h][t]->SetMarkerSize(markersize);
         canv->cd(1 + h + 3 * t);
         htime[h][t]->Draw(opt);
         htime[h][t]->Write();
      }
      canv->cd(3 + t * 3);
      htime_ratio[t]->Draw(opt); gPad->SetLogz();
      htime_ratio[t]->Write();
   }

   canv->cd(7); hsparse_mem->Draw(opt);
   canv->cd(8); hsparse_bins->Draw(opt);
   hsparse_mem->Write();
   hsparse_bins->Write();

   canv->Write();

   delete f;
}
 sparsehist.C:1
 sparsehist.C:2
 sparsehist.C:3
 sparsehist.C:4
 sparsehist.C:5
 sparsehist.C:6
 sparsehist.C:7
 sparsehist.C:8
 sparsehist.C:9
 sparsehist.C:10
 sparsehist.C:11
 sparsehist.C:12
 sparsehist.C:13
 sparsehist.C:14
 sparsehist.C:15
 sparsehist.C:16
 sparsehist.C:17
 sparsehist.C:18
 sparsehist.C:19
 sparsehist.C:20
 sparsehist.C:21
 sparsehist.C:22
 sparsehist.C:23
 sparsehist.C:24
 sparsehist.C:25
 sparsehist.C:26
 sparsehist.C:27
 sparsehist.C:28
 sparsehist.C:29
 sparsehist.C:30
 sparsehist.C:31
 sparsehist.C:32
 sparsehist.C:33
 sparsehist.C:34
 sparsehist.C:35
 sparsehist.C:36
 sparsehist.C:37
 sparsehist.C:38
 sparsehist.C:39
 sparsehist.C:40
 sparsehist.C:41
 sparsehist.C:42
 sparsehist.C:43
 sparsehist.C:44
 sparsehist.C:45
 sparsehist.C:46
 sparsehist.C:47
 sparsehist.C:48
 sparsehist.C:49
 sparsehist.C:50
 sparsehist.C:51
 sparsehist.C:52
 sparsehist.C:53
 sparsehist.C:54
 sparsehist.C:55
 sparsehist.C:56
 sparsehist.C:57
 sparsehist.C:58
 sparsehist.C:59
 sparsehist.C:60
 sparsehist.C:61
 sparsehist.C:62
 sparsehist.C:63
 sparsehist.C:64
 sparsehist.C:65
 sparsehist.C:66
 sparsehist.C:67
 sparsehist.C:68
 sparsehist.C:69
 sparsehist.C:70
 sparsehist.C:71
 sparsehist.C:72
 sparsehist.C:73
 sparsehist.C:74
 sparsehist.C:75
 sparsehist.C:76
 sparsehist.C:77
 sparsehist.C:78
 sparsehist.C:79
 sparsehist.C:80
 sparsehist.C:81
 sparsehist.C:82
 sparsehist.C:83
 sparsehist.C:84
 sparsehist.C:85
 sparsehist.C:86
 sparsehist.C:87
 sparsehist.C:88
 sparsehist.C:89
 sparsehist.C:90
 sparsehist.C:91
 sparsehist.C:92
 sparsehist.C:93
 sparsehist.C:94
 sparsehist.C:95
 sparsehist.C:96
 sparsehist.C:97
 sparsehist.C:98
 sparsehist.C:99
 sparsehist.C:100
 sparsehist.C:101
 sparsehist.C:102
 sparsehist.C:103
 sparsehist.C:104
 sparsehist.C:105
 sparsehist.C:106
 sparsehist.C:107
 sparsehist.C:108
 sparsehist.C:109
 sparsehist.C:110
 sparsehist.C:111
 sparsehist.C:112
 sparsehist.C:113
 sparsehist.C:114
 sparsehist.C:115
 sparsehist.C:116
 sparsehist.C:117
 sparsehist.C:118
 sparsehist.C:119
 sparsehist.C:120
 sparsehist.C:121
 sparsehist.C:122
 sparsehist.C:123
 sparsehist.C:124
 sparsehist.C:125
 sparsehist.C:126
 sparsehist.C:127
 sparsehist.C:128
 sparsehist.C:129
 sparsehist.C:130
 sparsehist.C:131
 sparsehist.C:132
 sparsehist.C:133
 sparsehist.C:134
 sparsehist.C:135
 sparsehist.C:136
 sparsehist.C:137
 sparsehist.C:138
 sparsehist.C:139
 sparsehist.C:140
 sparsehist.C:141
 sparsehist.C:142
 sparsehist.C:143
 sparsehist.C:144
 sparsehist.C:145
 sparsehist.C:146
 sparsehist.C:147
 sparsehist.C:148
 sparsehist.C:149
 sparsehist.C:150
 sparsehist.C:151
 sparsehist.C:152
 sparsehist.C:153
 sparsehist.C:154
 sparsehist.C:155
 sparsehist.C:156
 sparsehist.C:157
 sparsehist.C:158
 sparsehist.C:159
 sparsehist.C:160
 sparsehist.C:161
 sparsehist.C:162
 sparsehist.C:163
 sparsehist.C:164
 sparsehist.C:165
 sparsehist.C:166
 sparsehist.C:167
 sparsehist.C:168
 sparsehist.C:169
 sparsehist.C:170
 sparsehist.C:171
 sparsehist.C:172
 sparsehist.C:173
 sparsehist.C:174
 sparsehist.C:175
 sparsehist.C:176
 sparsehist.C:177
 sparsehist.C:178
 sparsehist.C:179
 sparsehist.C:180
 sparsehist.C:181
 sparsehist.C:182
 sparsehist.C:183
 sparsehist.C:184
 sparsehist.C:185
 sparsehist.C:186
 sparsehist.C:187
 sparsehist.C:188
 sparsehist.C:189
 sparsehist.C:190
 sparsehist.C:191
 sparsehist.C:192
 sparsehist.C:193
 sparsehist.C:194
 sparsehist.C:195
 sparsehist.C:196
 sparsehist.C:197
 sparsehist.C:198
 sparsehist.C:199
 sparsehist.C:200
 sparsehist.C:201
 sparsehist.C:202
 sparsehist.C:203
 sparsehist.C:204
 sparsehist.C:205
 sparsehist.C:206
 sparsehist.C:207
 sparsehist.C:208
 sparsehist.C:209
 sparsehist.C:210
 sparsehist.C:211
 sparsehist.C:212
 sparsehist.C:213
 sparsehist.C:214
 sparsehist.C:215
 sparsehist.C:216
 sparsehist.C:217
 sparsehist.C:218
 sparsehist.C:219
 sparsehist.C:220
 sparsehist.C:221
 sparsehist.C:222
 sparsehist.C:223
 sparsehist.C:224
 sparsehist.C:225
 sparsehist.C:226
 sparsehist.C:227
 sparsehist.C:228
 sparsehist.C:229
 sparsehist.C:230
 sparsehist.C:231
 sparsehist.C:232
 sparsehist.C:233
 sparsehist.C:234
 sparsehist.C:235
 sparsehist.C:236
 sparsehist.C:237
 sparsehist.C:238
 sparsehist.C:239
 sparsehist.C:240
 sparsehist.C:241
 sparsehist.C:242
 sparsehist.C:243
 sparsehist.C:244
 sparsehist.C:245
 sparsehist.C:246
 sparsehist.C:247
 sparsehist.C:248
 sparsehist.C:249
 sparsehist.C:250
 sparsehist.C:251
 sparsehist.C:252
 sparsehist.C:253
 sparsehist.C:254
 sparsehist.C:255
 sparsehist.C:256
 sparsehist.C:257
 sparsehist.C:258
 sparsehist.C:259
 sparsehist.C:260
 sparsehist.C:261
 sparsehist.C:262
 sparsehist.C:263
 sparsehist.C:264
 sparsehist.C:265
 sparsehist.C:266
 sparsehist.C:267
 sparsehist.C:268
 sparsehist.C:269
 sparsehist.C:270
 sparsehist.C:271
 sparsehist.C:272
 sparsehist.C:273
 sparsehist.C:274
 sparsehist.C:275
 sparsehist.C:276
 sparsehist.C:277
 sparsehist.C:278
 sparsehist.C:279
 sparsehist.C:280
 sparsehist.C:281
 sparsehist.C:282
 sparsehist.C:283
 sparsehist.C:284
 sparsehist.C:285
 sparsehist.C:286
 sparsehist.C:287
 sparsehist.C:288
 sparsehist.C:289
 sparsehist.C:290
 sparsehist.C:291
 sparsehist.C:292
 sparsehist.C:293
 sparsehist.C:294
 sparsehist.C:295
 sparsehist.C:296
 sparsehist.C:297
 sparsehist.C:298
 sparsehist.C:299
 sparsehist.C:300
 sparsehist.C:301
 sparsehist.C:302
 sparsehist.C:303
 sparsehist.C:304
 sparsehist.C:305
 sparsehist.C:306
 sparsehist.C:307
 sparsehist.C:308
 sparsehist.C:309
 sparsehist.C:310
 sparsehist.C:311
 sparsehist.C:312
 sparsehist.C:313
 sparsehist.C:314
 sparsehist.C:315
 sparsehist.C:316
 sparsehist.C:317
 sparsehist.C:318
 sparsehist.C:319
 sparsehist.C:320
 sparsehist.C:321
 sparsehist.C:322
 sparsehist.C:323
 sparsehist.C:324
 sparsehist.C:325
 sparsehist.C:326
 sparsehist.C:327
 sparsehist.C:328
 sparsehist.C:329
 sparsehist.C:330
 sparsehist.C:331
 sparsehist.C:332
 sparsehist.C:333
 sparsehist.C:334
 sparsehist.C:335
 sparsehist.C:336
 sparsehist.C:337
 sparsehist.C:338
 sparsehist.C:339
 sparsehist.C:340
 sparsehist.C:341
 sparsehist.C:342
 sparsehist.C:343
 sparsehist.C:344
 sparsehist.C:345
 sparsehist.C:346
 sparsehist.C:347
 sparsehist.C:348
 sparsehist.C:349
 sparsehist.C:350
 sparsehist.C:351
 sparsehist.C:352
 sparsehist.C:353
 sparsehist.C:354
 sparsehist.C:355
 sparsehist.C:356
 sparsehist.C:357
 sparsehist.C:358
 sparsehist.C:359
 sparsehist.C:360
 sparsehist.C:361
 sparsehist.C:362
 sparsehist.C:363
 sparsehist.C:364
 sparsehist.C:365
 sparsehist.C:366
 sparsehist.C:367
 sparsehist.C:368
 sparsehist.C:369
 sparsehist.C:370
 sparsehist.C:371
 sparsehist.C:372
 sparsehist.C:373
 sparsehist.C:374
 sparsehist.C:375
 sparsehist.C:376
 sparsehist.C:377
 sparsehist.C:378
 sparsehist.C:379
 sparsehist.C:380
 sparsehist.C:381
 sparsehist.C:382
 sparsehist.C:383
 sparsehist.C:384
 sparsehist.C:385
 sparsehist.C:386
 sparsehist.C:387
 sparsehist.C:388
 sparsehist.C:389
 sparsehist.C:390
 sparsehist.C:391
 sparsehist.C:392
 sparsehist.C:393
 sparsehist.C:394
 sparsehist.C:395
 sparsehist.C:396
 sparsehist.C:397
thumb