При компиляции вашего проекта 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="
" 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 секунд.
а что если при компиляции стоить только те проекты, которые были изменены? это может ускорить работу?
ОтветитьУдалитьИдея не новая но с NAnt получилось интересно.
ОтветитьУдалитьСпасибо.
Круто.
ОтветитьУдалитьУ меня еще была идея комплировать проекты на другом компьютере, чтобы освободить больше ресурсов для компиляции.
Но так и не сообразил как это реализовать.
А оно вообще стабильно работает? :-) Как относится к перебоям питания? Ладно студия, но временные файлы винды...
ОтветитьУдалитьОдна проблема - памяти жалко! :)
ОтветитьУдалитьЯ знаю еще один путь для ускорения сборки - нужно взять 4xSSD и поставить их в Stripe RAID, думаю, что ускорение будет не меньше :)
Сколько памяти надо для ваших проектов ?
ОтветитьУдалить@Атрём
ОтветитьУдалитьКонечно это ускорит, даже когда все компилируется в памяти надо выставлять эту опцию
@Игорь
ОтветитьУдалитьВсе зависит от настроек виртуального диска. Мы используем http://download.cnet.com/RamDisk-by-SuperSpeed-Software-Inc/3000-2122_4-65231.html
У него, например, есть опция сохранять все данные на жестком диске при выключении компьютера. Тут опять же все зависит от ваших потребностей. Вы что в Temp храните?)
@Mad Hollander
ОтветитьУдалитьНа Temp и все bin и obj у меня уходит 400М.
На счет ссылки http://kxlm.blogspot.com/2009/05/visual-studio_04.html
ОтветитьУдалитьСначала она не работала и я подумал, что это бот написал =)
Отличная реализация на PowerShell. Спасибо.
Способ хорош, но вот у нас в солюшене 120 проектов и размеры одних только папок bin суммарно дают 1.5 Гигабайта - нехватает RAM слегка :(
ОтветитьУдалить@megaboich
ОтветитьУдалитьХа, да уж. Если не секрет, сколько занимает сборка всего проекта?
Шаг 4 не плохо комбинировать с ключом /MP#, где # = количество тредов. Это позволяет распараллеливать на уровне сpp, а не только на уровне проджектов.
ОтветитьУдалитьКруто! Спасибо за наводку.
ОтветитьУдалитьМожно ли скопировать весь проект на виртуальный диск, а не только каталоги bin&obj ?
ОтветитьУдалить@Sergey
ОтветитьУдалитьЯ думаю, что можно.
Вот очень интересная статья по теме ускорения без виртуальных дисков http://lennybacon.com/2010/10/18/UltimateGuideToSpeedUpVisualStudio.aspx