Author Topic: Optimizing Makefilegenerator  (Read 20744 times)

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Optimizing Makefilegenerator
« on: November 23, 2005, 08:43:23 am »
After seeing how long large projects take to calculate the compilation commands, i realized that the string replacing is EXTREMELY SUBOPTIMAL.

    compilerCmd.Replace(_T("$compiler"), compilerStr);
    compilerCmd.Replace(_T("$linker"), m_CompilerSet->GetPrograms().LD);
    compilerCmd.Replace(_T("$lib_linker"), m_CompilerSet->GetPrograms().LIB);
    compilerCmd.Replace(_T("$rescomp"), m_CompilerSet->GetPrograms().WINDRES);

etc.

My proposal is to use wxStringTokenizer to search for the macros (using '$' as token), and do the replacing manually using the Matches() function. This will allow us to speed up the generation time by around 15x.

Objections? Comments?

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #1 on: November 23, 2005, 09:14:16 am »
It's not replace that slows down this process. It's the calls to wxFileName::Normalize. For each file and for each build target, filenames are recalculated. We have lowered this overhead a lot by caching the calculated filenames and recalculating them when needed.
It will be optimized even more with the optimizations Thomas will do to project loading.

And all these are only noticeable in *large* projects like C::B itself...
Be patient!
This bug will be fixed soon...

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #2 on: November 23, 2005, 05:08:52 pm »
Awww... and *just* when I came up with the idea of a cacheable search-and-replace class... :(  :lol:

Anyway, how did you find out the normalizing was the most time-consuming part?
Do you happen to have a profiler report? (I'd love to see it)
Can anybody help us with the profiling?
« Last Edit: November 23, 2005, 05:29:42 pm by rickg22 »

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #3 on: November 23, 2005, 05:38:35 pm »
Hey wait a minute!

I just noticed something VERY SUSPICIOUS.

Code
lines 224 and following:
    if (target &&
        target->GetParentProject()->GetModeForPCH() == pchObjectDir)
    {
        wxArrayString includedDirs; // avoid adding duplicate dirs...
        wxString sep = wxFILE_SEP_PATH;
        // find all PCH in project
        int count = target->GetParentProject()->GetFilesCount();
        for (int i = 0; i < count; ++i)
        {

Does this loop get executed EVERYTIME CreateSingleFileCompileCmd is invoked? :shock: That adds QUADRATIC complexity! :shock:

Oh wait, nevermind, that option is not executed in my setup.... darn, I really need profiler info...
« Last Edit: November 23, 2005, 05:40:48 pm by rickg22 »

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #4 on: November 23, 2005, 05:52:24 pm »
MakefileGenerator is used as a quick hack to generate the build command line. It will need to be rewritten anyway as it doesn't support many of the newest features.
I will implement another optimized command-line generator for builds.
It won't take long...
Be patient!
This bug will be fixed soon...

Offline Ceniza

  • Developer
  • Lives here!
  • *****
  • Posts: 1441
    • CenizaSOFT
Re: Optimizing Makefilegenerator
« Reply #5 on: November 23, 2005, 06:14:54 pm »
Quote from: mandrav
I will implement another optimized command-line generator for builds.

Psssst, any possibility to add the so wanted (at least for me) multithreaded build (a.k.a. make -j# clone)?

Maybe that way it could't take you a bit longer :wink:

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #6 on: November 23, 2005, 07:02:58 pm »
Quote from: mandrav
I will implement another optimized command-line generator for builds.

Psssst, any possibility to add the so wanted (at least for me) multithreaded build (a.k.a. make -j# clone)?

Maybe that way it could't take you a bit longer :wink:

The problem with that is that wxExecute can't be used inside any thread other than the main...
Be patient!
This bug will be fixed soon...

Offline Ceniza

  • Developer
  • Lives here!
  • *****
  • Posts: 1441
    • CenizaSOFT
Re: Optimizing Makefilegenerator
« Reply #7 on: November 23, 2005, 07:24:06 pm »
So it should be made in the main thread... evil.

I'll play a bit with it and see what I find.

Offline Urxae

  • Regular
  • ***
  • Posts: 376
Re: Optimizing Makefilegenerator
« Reply #8 on: November 23, 2005, 07:57:48 pm »
Psssst, any possibility to add the so wanted (at least for me) multithreaded build (a.k.a. make -j# clone)?

Maybe that way it could't take you a bit longer :wink:

The problem with that is that wxExecute can't be used inside any thread other than the main...

But you can still have it be called asynchronously, right? So maybe you could use some mechanism (events?) to tell the main thread to call wxExecute, which can then be activated from other threads?

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #9 on: November 23, 2005, 08:18:42 pm »
I will implement another optimized command-line generator for builds.
It won't take long...

I want to help I want to help I want to help! :o

Offline Ceniza

  • Developer
  • Lives here!
  • *****
  • Posts: 1441
    • CenizaSOFT
Re: Optimizing Makefilegenerator
« Reply #10 on: November 23, 2005, 08:19:38 pm »
Quote from: Urxae
But you can still have it be called asynchronously, right? So maybe you could use some mechanism (events?) to tell the main thread to call wxExecute, which can then be activated from other threads?

That'd be the hacky idea: You! Yes, you! The one able to call wxExecute! Call it with these parameters for me! Now!

The function could also try to handle it all avoiding the use of threads.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Optimizing Makefilegenerator
« Reply #11 on: November 23, 2005, 08:25:02 pm »
It is actually pretty easy to call several compiler instances asynchronously. But it is an awful mess to sort out the compiler's output from several running processes.
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #12 on: November 23, 2005, 08:32:16 pm »
I never said it isn't possible ;)
It's just that the last time this subject was brought up, the usage of the thread pool was discussed.

Anyway, this is deliberately not being currently worked on because the compiler will be redesigned so...
Be patient!
This bug will be fixed soon...

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
FIRST RESULTS
« Reply #13 on: November 24, 2005, 07:01:03 am »
I added 15 timers using wxStopwatch (they're static variables, they're reset to 0 when compilation starts). Here are the results:

Code
SDK Only
----------

Timer  1: 0 ms
Timer  2: 5046 ms
Timer  3: 1312 ms
Timer  4: 0 ms
Timer  5: 1189 ms
Timer  6: 0 ms
Timer  7: 3142 ms
Timer  8: 15 ms
Timer  9: 0 ms
Timer 10: 1577 ms
Timer 11: 16 ms
Timer 12: 0 ms
Timer 13: 218 ms
Timer 14: 48 ms
Timer 15: 0 ms

All Targets
---------------
Timer  1: 0 ms
Timer  2: 17007 ms
Timer  3: 3243 ms
Timer  4: 0 ms
Timer  5: 3733 ms
Timer  6: 16 ms
Timer  7: 10747 ms
Timer  8: 78 ms
Timer  9: 0 ms
Timer 10: 5030 ms
Timer 11: 32 ms
Timer 12: 0 ms
Timer 13: 894 ms
Timer 14: 110 ms
Timer 15: 0 ms

These times reflect the calculation of the compiling commands ONLY. The actual compilation times are not taken into account. The compilation method was a full rebuild.

And now, the (shameful? :lol: ) modified source code of the routine in question:

Code
wxString MakefileGenerator::CreateSingleFileCompileCmd(const wxString& command,
                                                        ProjectBuildTarget* target,
                                                        ProjectFile* pf,
                                                        const wxString& file,
                                                        const wxString& object,
                                                        const wxString& deps)
{
    // in case of linking command, deps has resource objects
    UpdateCompiler(target);
    wxStopWatch sw;
    wxString compilerStr;
    if (pf)
    {
        if (pf->compilerVar.Matches(_T("CPP")))
            compilerStr = m_CompilerSet->GetPrograms().CPP;
        else if (pf->compilerVar.Matches(_T("CC")))
            compilerStr = m_CompilerSet->GetPrograms().C;
        else if (pf->compilerVar.Matches(_T("WINDRES")))
            compilerStr = m_CompilerSet->GetPrograms().WINDRES;
        else
            return wxEmptyString; // unknown compiler var
    }
    else
    {
    wxFileName fname(file);
    if (fname.GetExt().Lower().Matches(_T("c")))
            compilerStr = m_CompilerSet->GetPrograms().C;
        else
            compilerStr = m_CompilerSet->GetPrograms().CPP;
    }
    time1 += sw.Time();
    sw.Start();
    wxString cflags;
    wxString global_cflags;
wxString prj_cflags;
DoAppendCompilerOptions(global_cflags, 0L, true);
DoAppendCompilerOptions(prj_cflags, 0L);
    DoGetMakefileCFlags(cflags, target);
    time2 += sw.Time();
    sw.Start();

    if (target)
    {
        cflags.Replace(_T("$(") + target->GetTitle() + _T("_GLOBAL_CFLAGS)"), global_cflags);
        cflags.Replace(_T("$(") + target->GetTitle() + _T("_PROJECT_CFLAGS)"), prj_cflags);
    }
    else if (!target && !pf) // probably single file compilation
        cflags = global_cflags;

    wxString ldflags;
wxString global_ldflags;
wxString prj_ldflags;
DoAppendLinkerOptions(global_ldflags, 0L, true);
DoAppendLinkerOptions(prj_ldflags, 0L);
DoGetMakefileLDFlags(ldflags, target);
    time3 += sw.Time();
    sw.Start();
    if (target)
    {
        ldflags.Replace(_T("$(") + target->GetTitle() + _T("_GLOBAL_LDFLAGS)"), global_ldflags);
        ldflags.Replace(_T("$(") + target->GetTitle() + _T("_PROJECT_LDFLAGS)"), prj_ldflags);
    }
    else if (!target && !pf) // probably single file compilation
        ldflags = global_ldflags;

    time4 += sw.Time();
    sw.Start();
    wxString ldadd;
wxString global_ldadd;
wxString prj_ldadd;
DoAppendLinkerLibs(global_ldadd, 0L, true);
DoAppendLinkerLibs(prj_ldadd, 0L);
DoGetMakefileLibs(ldadd, target);
    time5 += sw.Time();
    sw.Start();
    if (target)
    {
        ldadd.Replace(_T("$(") + target->GetTitle() + _T("_GLOBAL_LIBS)"), global_ldadd);
        ldadd.Replace(_T("$(") + target->GetTitle() + _T("_PROJECT_LIBS)"), prj_ldadd);
    }
    else if (!target && !pf) // probably single file compilation
        ldadd = global_ldadd;

    time6 += sw.Time();
    sw.Start();
wxString global_res_incs;
wxString prj_res_incs;
wxString res_incs;
DoAppendResourceIncludeDirs(global_res_incs, 0L, m_CompilerSet->GetSwitches().includeDirs, true);
DoAppendResourceIncludeDirs(prj_res_incs, 0L, m_CompilerSet->GetSwitches().includeDirs);
res_incs << global_res_incs << _T(" ") << prj_res_incs << _T(" ");
DoAppendResourceIncludeDirs(res_incs, target, m_CompilerSet->GetSwitches().includeDirs);

    wxString incs;
wxString global_incs;
wxString prj_incs;
DoAppendIncludeDirs(global_incs, 0L, m_CompilerSet->GetSwitches().includeDirs, true);
DoAppendIncludeDirs(prj_incs, 0L, m_CompilerSet->GetSwitches().includeDirs);
DoGetMakefileIncludes(incs, target);

    time7 += sw.Time();
    sw.Start();
    if (target)
    {
        incs.Replace(_T("$(") + target->GetTitle() + _T("_GLOBAL_INCS)"), global_incs);
        incs.Replace(_T("$(") + target->GetTitle() + _T("_PROJECT_INCS)"), prj_incs);
    }
    else if (!target && !pf) // probably single file compilation
        incs = global_incs;

    // for PCH to work, the very first include dir *must* be the object output dir
    // *only* if PCH is generated in the object output dir
    time8 += sw.Time();
    sw.Start();
    if (target &&
        target->GetParentProject()->GetModeForPCH() == pchObjectDir)
    {
        wxArrayString includedDirs; // avoid adding duplicate dirs...
        wxString sep = wxFILE_SEP_PATH;
        // find all PCH in project
        int count = target->GetParentProject()->GetFilesCount();
        for (int i = 0; i < count; ++i)
        {
            ProjectFile* f = target->GetParentProject()->GetFile(i);
            if (FileTypeOf(f->relativeFilename) == ftHeader &&
                f->compile)
            {
                // it is a PCH; add it's object dir to includes
                wxString dir = wxFileName(target->GetObjectOutput() + sep + f->GetObjName()).GetPath();
                if (includedDirs.Index(dir) == wxNOT_FOUND)
                {
                    includedDirs.Add(dir);
                    incs = m_CompilerSet->GetSwitches().includeDirs +
                            dir +
                            _T(" ") +
                            incs;
                }
            }
        }
    }

    time9 += sw.Time();
    sw.Start();
    wxString libs;
wxString global_libs;
wxString prj_libs;
DoAppendLibDirs(global_libs, 0L, m_CompilerSet->GetSwitches().libDirs, true);
DoAppendLibDirs(prj_libs, 0L, m_CompilerSet->GetSwitches().libDirs);
DoGetMakefileLibDirs(libs, target);

    time10 += sw.Time();
    sw.Start();
    if (target)
    {
        libs.Replace(_T("$(") + target->GetTitle() + _T("_GLOBAL_LIBDIRS)"), global_libs);
        libs.Replace(_T("$(") + target->GetTitle() + _T("_PROJECT_LIBDIRS)"), prj_libs);
    }
    else if (!target && !pf) // probably single file compilation
        libs = global_libs;

    time11 += sw.Time();
    sw.Start();

    wxString output;
    if (target)
        output = UnixFilename(target->GetOutputFilename());
    else
    {
        wxString object_unquoted(object);
        if (!object_unquoted.IsEmpty() && object_unquoted.GetChar(0) == '"')
            object_unquoted.Replace(_T("\""), _T(""));
        wxFileName fname(object_unquoted);
        fname.SetExt(EXECUTABLE_EXT);
        output = fname.GetFullPath();
    }

    time12 += sw.Time();
    sw.Start();

    Manager::Get()->GetMacrosManager()->ReplaceEnvVars(output);
    ConvertToMakefileFriendly(output);
    QuoteStringIfNeeded(output);

    wxString linkobjs;

    time13 += sw.Time();
    sw.Start();

    wxString compilerCmd = command;
    compilerCmd.Replace(_T("$compiler"), compilerStr);
    compilerCmd.Replace(_T("$linker"), m_CompilerSet->GetPrograms().LD);
    compilerCmd.Replace(_T("$lib_linker"), m_CompilerSet->GetPrograms().LIB);
    compilerCmd.Replace(_T("$rescomp"), m_CompilerSet->GetPrograms().WINDRES);
    compilerCmd.Replace(_T("$options"), cflags);
    compilerCmd.Replace(_T("$link_options"), ldflags);
    compilerCmd.Replace(_T("$includes"), incs);
    compilerCmd.Replace(_T("$res_includes"), res_incs);
    compilerCmd.Replace(_T("$libdirs"), libs);
    compilerCmd.Replace(_T("$libs"), ldadd);
    compilerCmd.Replace(_T("$file"), file);
    compilerCmd.Replace(_T("$dep_object"), deps);
    compilerCmd.Replace(_T("$object"), object);
    compilerCmd.Replace(_T("$exe_output"), output);
    compilerCmd.Replace(_T("$resource_output"), object);
    compilerCmd.Replace(_T("$link_resobjects"), deps);
    compilerCmd.Replace(_T("$link_objects"), object);
    // the following were added to support the QUICK HACK
    // at directcommands.cpp:576
    compilerCmd.Replace(_T("$+link_objects"), object);
    compilerCmd.Replace(_T("$-link_objects"), object);
    compilerCmd.Replace(_T("$-+link_objects"), object);
    compilerCmd.Replace(_T("$+-link_objects"), object);

    time14 += sw.Time();
    sw.Start();

    if (target && (target->GetTargetType() == ttStaticLib || target->GetTargetType() == ttDynamicLib))
    {
        wxFileName fname(target->GetOutputFilename());
        if (!fname.GetName().StartsWith(m_CompilerSet->GetSwitches().libPrefix))
            fname.SetName(m_CompilerSet->GetSwitches().libPrefix + fname.GetName());
        fname.SetExt(m_CompilerSet->GetSwitches().libExtension);
        wxString out = UnixFilename(fname.GetFullPath());
        ConvertToMakefileFriendly(out);
        QuoteStringIfNeeded(out);
        if (target->GetTargetType() == ttStaticLib || target->GetCreateStaticLib())
            compilerCmd.Replace(_T("$static_output"), out);
        else
        {
            compilerCmd.Replace(_T("-Wl,--out-implib=$static_output"), _T("")); // special gcc case
            compilerCmd.Replace(_T("$static_output"), _T(""));
        }
        if (target->GetCreateDefFile())
        {
            fname.SetExt(_T("def"));
            out = UnixFilename(fname.GetFullPath());
            ConvertToMakefileFriendly(out);
            QuoteStringIfNeeded(out);
            compilerCmd.Replace(_T("$def_output"), out);
        }
        else
        {
            compilerCmd.Replace(_T("-Wl,--output-def=$def_output"), _T("")); // special gcc case
            compilerCmd.Replace(_T("$def_output"), _T(""));
        }
    }

    time15 += sw.Time();

#ifndef __WXMSW__
    // run the command in a shell, so backtick'd expressions can be evaluated
    compilerCmd = m_Compiler->GetConsoleShell() + _T(" '") + compilerCmd + _T("'");
#endif
    return compilerCmd;
}


I hope these timings help us determine which takes longer and why.

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #14 on: November 24, 2005, 09:12:35 am »
I 'm sure there is something wrong with the numbers you posted:

Quote
Timer  2: 5046 ms

5 seconds for this?

Quote
Code
    sw.Start();
    wxString cflags;
    wxString global_cflags;
wxString prj_cflags;
DoAppendCompilerOptions(global_cflags, 0L, true);
DoAppendCompilerOptions(prj_cflags, 0L);
    DoGetMakefileCFlags(cflags, target);
    time2 += sw.Time();

And you mean to tell me that you 're waiting 40+ seconds only for commands generation when building the project?
What is your PC's specs?
Have you disabled this safe_but_slow tweak of yours?
Be patient!
This bug will be fixed soon...

Offline Urxae

  • Regular
  • ***
  • Posts: 376
Re: Optimizing Makefilegenerator
« Reply #15 on: November 24, 2005, 12:00:09 pm »
Well, DoAppendCompilerOptions does call MacrosManager::ReplaceEnvVars, which calls MacrosManager::ReplaceMacros, which looks like it might take a lot of time. String operations tend to be more expensive that one would think.
That, and we don't yet know his system specs of course.

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Optimizing Makefilegenerator
« Reply #16 on: November 24, 2005, 01:11:53 pm »
Still...
40+ seconds?!?  :shock:
Be patient!
This bug will be fixed soon...

Offline Urxae

  • Regular
  • ***
  • Posts: 376
Re: Optimizing Makefilegenerator
« Reply #17 on: November 24, 2005, 02:41:58 pm »
Well, I have noticed on my computer (750 MHz laptop, 256 MB RAM) that it can take quite some time before C::B starts the first g++ when compiling itself. I'm not sure if it differs if more files need recompiling, and I don't feel like trying: a full rebuild usually takes around an hour on this machine.
Yes I know, I need a better machine :(.

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #18 on: November 24, 2005, 05:24:51 pm »
Yiannis:

I got an old Athlon 1800+ running at 1.5 GHz. My machine isn't very fast. And YES, I *did* disable the stability tweak.

Second: The measurement used by wxStopWatch isn't very accurate. This, and taking into account that the times are
calculated in VERY TIGHT loops and then added. CPUs don't use subatomic clocks for
time measurement, and noticing that each call to this routine may take about 10ms, i'm sure there are cumulative rounding errors.
(The full-rebuild version takes about 10 seconds to calculate, and the SDK test takes about 5 seconds. So I'd say the reported times are 4X the real time ellapsed.)

However, we should pay attention to the RELATIVE times. So call them milliseconds, or clock ticks, the point is that some parts of the routine take much longer than they should.
Remember you told me that the string replacing parts weren't very CPU-consuming? Well, the reported time was less than 5% the total times for the calculation.

Also take into account the percentages. The numbers for the SDK calculation are proportional to the numbers for the full-rebuild calculation. So the numbers do make sense.

Edit: here they are, the normalized times.
Code
Timer  1: 0
Timer  2: 5046 ms (40.16%)
Timer  3: 1312 ms (10.44%)
Timer  4: 0 ms
Timer  5: 1189 ms (9.46%)
Timer  6: 0 ms
Timer  7: 3142 ms (25.01%)
Timer  8: 15 ms   (0.12%)
Timer  9: 0 ms
Timer 10: 1577 ms (12.55%)
Timer 11: 16 ms   (0.13%)
Timer 12: 0 ms
Timer 13: 218 ms  (1.73%)
Timer 14: 48 ms   (0.38%)
Timer 15: 0 ms

Total "time": 12563

All Targets
---------------
Timer  1: 0 ms
Timer  2: 17007 ms (41.61%)
Timer  3: 3243 ms  (7.93%)
Timer  4: 0 ms
Timer  5: 3733 ms  (9.13%)
Timer  6: 16 ms    (0.04%)
Timer  7: 10747 ms (26.29%)
Timer  8: 78 ms    (0.19%)
Timer  9: 0 ms
Timer 10: 5030 ms  (12.30%)
Timer 11: 32 ms    (0.08%)
Timer 12: 0 ms
Timer 13: 894 ms   (2.19%)
Timer 14: 110 ms   (0.27%)
Timer 15: 0 ms

Total "time": 40874

So, for both cases, timer2 gets 41%, timer7 gets 25% and timer10 gets 12%.

Now, how are we gonna solve this?
We could, for example, move some of the calculations to outside the loop (that calls the function in question), because they're dependent on the target, not on the filename. I'd appreciate if you gave more details on what strings exactly you're generating. All those replaced macros get me dizzy.
« Last Edit: November 24, 2005, 05:43:42 pm by rickg22 »

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #19 on: November 25, 2005, 08:13:20 am »
Good news people! Yiannis sent me a prototype for an optimized version, and it's got rid of almost all of the bottleneck!

I measured the full-rebuild calculation times, with and without patch.

Without patch: 19.16 seconds (with manual stopwatch)
With patch: 01.80 seconds  (with manual stopwatch). This varies on occasions, up to 4 seconds. I'm not sure why.

Now we have only to take care of some minor details and a linking bug, and we're done :)

Offline Ceniza

  • Developer
  • Lives here!
  • *****
  • Posts: 1441
    • CenizaSOFT
Re: Optimizing Makefilegenerator
« Reply #20 on: November 25, 2005, 08:51:13 am »
Sounds good :)

Offline rickg22

  • Lives here!
  • ****
  • Posts: 2283
Re: Optimizing Makefilegenerator
« Reply #21 on: November 28, 2005, 07:05:58 am »
More good news, people! I fully revamped the MacrosManager, and this alone reduced the full rebuild calculation time from 13 seconds to 1.7! :shock:

For those curious, makefile generation is now practically *instant*. Takess less than a second now!

(Yes, I rebuilt codeblocks, compared makefile generation and everything works exactly the same. Ceniza compared, too!)

I can't wait till Yiannis uploads his part of the patch :D