Ускоряем сборку проектов в Visual Studio

3 мая 2009 г.

При компиляции вашего проекта Visual Studio должна собрать больше 20 проектов? Значит вы, как и я, уже заметили, что эта операция занимает прилично времени. Сейчас у нас за раз компилируется 27 проектов и мы нашли способ, как ускорить этот процесс.

Шаг №1

Для начала надо сделать жесткий диск в памяти. Подойдет любая программа, которая на это способна (например, RamDisk).

Размер диска зависит от ваших потребностей. В итоге, на диске будут все временные файлы Windows, а также все папки bin и obj со сборками ваших проектов.

Шаг №2

Создаем на этом диске папку Temp и настраиваем на нее все пути до папок с временными файлами в системе.

Шаг №3

Теперь нам понадобится программа linkd. Все папки bin и obj нашего проекта должны быть ссылками на созданный жесткий диск в памяти.

Для дальнейшей работы файл linkd.exe необходимо скопировать в c:\WINDOWS\system32.

С помощью этой программы мы можем создать папку bin нашего проекта на диске в памяти и сделать на него ссылку из настоящей папки проекта. За счет того, что Visual Studio будет компилировать проекты на диск в оперативной памяти, мы и хотим получить ускорение. Такую операцию надо проделать со всеми папками bin и obj всех проектов, которые участвуют в сборке. Я написал скрипт на NAnt, который сделает это автоматически, т.к. я люблю все автоматизировать. Этот скрипт берет из файла конфигурации путь до корневой папки с проектами и название диска в памяти.

Файл SetUpRamDisk.build содержит скрипт:

   1:  <?xml version="1.0"?>
   2:  <!-- Link bin and obj folders to RAM disk -->
   3:  <project default="help">
   4:      <property name="directory.current" value="${project::get-base-directory()}" />
   5:      <property name="directory.system32" value="${environment::get-variable('windir')}\system32" />    
   6:      <property name="configuration.file" value="${directory.current}\SetUpRamDisk.xml" />
   7:      <property name="NL" value="&#x0D;&#x0A;" readonly="true" />
   8:      <property name="file.linker" value="${directory.current}\linker.bat" />
   9:      <property name="file.linker.execute" value="${directory.current}\linker-execute.bat" />
  10:      
  11:      
  12:      <property name="directory.project-to-ram" value="" dynamic="true" />
  13:      <property name="ram-disk-name" value="" dynamic="true" />
  14:      
  15:      <target name="go" depends="setup">
  16:          <copy file="${directory.current}\linkd.exe" todir="${directory.system32}" overwrite="true" failonerror="false"/>
  17:          
  18:          <property name="directories" value="" dynamic="true"/>
  19:          <foreach item="Folder" property="foldername">
  20:              <in>
  21:                  <items basedir="${directory.project-to-ram}" >
  22:                      <include name="**\bin"/>
  23:                      <include name="**\obj"/>
  24:                  </items>
  25:              </in>
  26:              <do>
  27:                  <if test="${string::get-length(directories) > 0}">
  28:                      <property name="directories" value="${directories},${foldername}" if="${string::get-length(directories) > 0}"/>
  29:                  </if>
  30:                  <if test="${string::get-length(directories) == 0}">
  31:                      <property name="directories" value="${foldername}"/>
  32:                  </if>
  33:              </do>
  34:          </foreach>
  35:              
  36:          <property name="create-folders" value="" dynamic="true"/>
  37:          <foreach item="String" in="${directories}" delim="," property="directoryPath">
  38:              <property name="create-folders" value="${create-folders}${NL}md ${string::replace(string::to-lower(directoryPath), 'd:', ram-disk-name)}" if="${string::get-length(directoryPath) > 0}"/>
  39:          </foreach>
  40:          
  41:          <foreach item="String" in="${directories}" delim="," property="directoryPath">
  42:              <delete dir="${directoryPath}" if="${string::get-length(directoryPath) > 0}" failonerror="false"/>
  43:          </foreach>
  44:          
  45:          <property name="link-folders" value="" dynamic="true"/>
  46:          <foreach item="String" in="${directories}" delim="," property="directoryPath">
  47:              <property name="link-folders" value="${link-folders}${NL}linkd ${directoryPath} ${string::replace(string::to-lower(directoryPath), 'd:', ram-disk-name)}" if="${string::get-length(directoryPath) > 0}"/>
  48:          </foreach>
  49:          
  50:          <property name="toFile" value="${create-folders}${link-folders}"/>
  51:          
  52:          <copy file="${file.linker}" tofile="${file.linker.execute}" overwrite="true">
  53:              <filterchain>
  54:                  <replacestring from="***SCRIPT_TO_LINK***" to="${toFile}"/>
  55:              </filterchain>
  56:          </copy>    
  57:          
  58:          <exec program="${file.linker.execute}"/>
  59:          
  60:          <delete file="${file.linker.execute}" failonerror="false" />    
  61:      </target>
  62:      
  63:      <target name="setup">
  64:          <xmlpeek file="${configuration.file}" xpath="configuration/project/add[@key='directory']/@value" property="directory.project-to-ram" />
  65:          <xmlpeek file="${configuration.file}" xpath="configuration/project/add[@key='ram-disk-name']/@value" property="ram-disk-name" />
  66:      </target>
  67:      
  68:      <target name="help">
  69:          <echo message="Don't execute this file directly" />
  70:      </target>
  71:  </project>

Если у кого-то есть желание, этот скрипт можно порефакторить или переписать на другой язык. Чтобы линковка всех папок проекта прошла правильно, нужно записать ваши параметры в файл конфигурации.

Файл SetUpRamDisk.xml хранит конфигурацию для скрипта:

   1:  <configuration>
   2:      <project>
   3:          <add key="ram-disk-name" value="R:"/>
   4:          <add key="directory" value="d:\Work\Projects\[КОРНЕВАЯ ПАПКА ВАШЕГО ПРОЕКТА]"/>
   5:      </project>
   6:  </configuration>

Всего-то надо вписать имя вашего диска в памяти и путь до проекта. Скрип найдет все папки bin и obj ваших проектов, сделает их ссылками на диск в памяти. Кстати, диск в памяти будет содержать структуру вашего проекта.

Для нормальной работы скрипта надо создать еще один файл - linker.bat. Он нужен только для того, чтобы в него записался сгерерированный скрипт и будет содержать только одну строчку:

***SCRIPT_TO_LINK***

Запускаем скрипт с помощью строки:

NAnt.exe /f:SetUpRamDisk.build go

Шаг №4

И последнее, идем в настройки самой Visual Studio и выставляем количество проектов, которое может компилироваться параллельно. Эта цифра зависит от вашего компьютера и самого проекта.

Результат

После всего описанного полная сборка проектов ускорилась почти в 2 раза. Перекомпиляция проекта после нескольких изменений происходит практически за 5 секунд.

16 комментариев:

  1. а что если при компиляции стоить только те проекты, которые были изменены? это может ускорить работу?

    ОтветитьУдалить
  2. Идея не новая но с NAnt получилось интересно.

    Спасибо.

    ОтветитьУдалить
  3. Круто.

    У меня еще была идея комплировать проекты на другом компьютере, чтобы освободить больше ресурсов для компиляции.

    Но так и не сообразил как это реализовать.

    ОтветитьУдалить
  4. А оно вообще стабильно работает? :-) Как относится к перебоям питания? Ладно студия, но временные файлы винды...

    ОтветитьУдалить
  5. Одна проблема - памяти жалко! :)

    Я знаю еще один путь для ускорения сборки - нужно взять 4xSSD и поставить их в Stripe RAID, думаю, что ускорение будет не меньше :)

    ОтветитьУдалить
  6. Сколько памяти надо для ваших проектов ?

    ОтветитьУдалить
  7. @Атрём
    Конечно это ускорит, даже когда все компилируется в памяти надо выставлять эту опцию

    ОтветитьУдалить
  8. @Игорь
    Все зависит от настроек виртуального диска. Мы используем http://download.cnet.com/RamDisk-by-SuperSpeed-Software-Inc/3000-2122_4-65231.html
    У него, например, есть опция сохранять все данные на жестком диске при выключении компьютера. Тут опять же все зависит от ваших потребностей. Вы что в Temp храните?)

    ОтветитьУдалить
  9. @Mad Hollander
    На Temp и все bin и obj у меня уходит 400М.

    ОтветитьУдалить
  10. На счет ссылки http://kxlm.blogspot.com/2009/05/visual-studio_04.html
    Сначала она не работала и я подумал, что это бот написал =)
    Отличная реализация на PowerShell. Спасибо.

    ОтветитьУдалить
  11. Способ хорош, но вот у нас в солюшене 120 проектов и размеры одних только папок bin суммарно дают 1.5 Гигабайта - нехватает RAM слегка :(

    ОтветитьУдалить
  12. @megaboich
    Ха, да уж. Если не секрет, сколько занимает сборка всего проекта?

    ОтветитьУдалить
  13. Шаг 4 не плохо комбинировать с ключом /MP#, где # = количество тредов. Это позволяет распараллеливать на уровне сpp, а не только на уровне проджектов.

    ОтветитьУдалить
  14. Можно ли скопировать весь проект на виртуальный диск, а не только каталоги bin&obj ?

    ОтветитьУдалить
  15. @Sergey
    Я думаю, что можно.

    Вот очень интересная статья по теме ускорения без виртуальных дисков http://lennybacon.com/2010/10/18/UltimateGuideToSpeedUpVisualStudio.aspx

    ОтветитьУдалить

Моя книга «Антихрупкость в IT»

Как достигать результатов в IT-проектах в условиях неопределённости. Подробнее...