Ще помогне ли паралелизмът за производителността на заключен обект, трябва ли да се изпълнява с една нишка или има друга техника?
Забелязах, че при достъп до набор от данни и добавяне на редове от множество нишки бяха хвърлени изключения. Затова създадох „безопасна за нишки“ версия за добавяне на редове чрез заключване на таблицата преди актуализиране на реда. Това внедряване работи, но изглежда бавно при много транзакции.
public partial class HaMmeRffl
{
public partial class PlayerStatsDataTable
{
public void AddPlayerStatsRow(int PlayerID, int Year, int StatEnum, int Value, DateTime Timestamp)
{
lock (TeamMemberData.Dataset.PlayerStats)
{
HaMmeRffl.PlayerStatsRow testrow = TeamMemberData.Dataset.PlayerStats.FindByPlayerIDYearStatEnum(PlayerID, Year, StatEnum);
if (testrow == null)
{
HaMmeRffl.PlayerStatsRow newRow = TeamMemberData.Dataset.PlayerStats.NewPlayerStatsRow();
newRow.PlayerID = PlayerID;
newRow.Year = Year;
newRow.StatEnum = StatEnum;
newRow.Value = Value;
newRow.Timestamp = Timestamp;
TeamMemberData.Dataset.PlayerStats.AddPlayerStatsRow(newRow);
}
else
{
testrow.Value = Value;
testrow.Timestamp = Timestamp;
}
}
}
}
}
Сега мога да извикам това безопасно от множество нишки, но всъщност купува ли ми нещо? Мога ли да направя това по различен начин за по-добра производителност. Например има ли начин да се използва пространство от имена System.Collections.Concurrent
за оптимизиране на производителността или други методи?
Освен това актуализирам основната база данни, след като целият набор от данни е актуализиран и това отнема много време. Това ще се счита ли за I/O операция и дали си струва да използвате паралелна обработка, като я актуализирате, след като всеки ред се актуализира в набора от данни (или определен брой редове).
АКТУАЛИЗАЦИЯ
Написах някакъв код за тестване на едновременна срещу последователна обработка, който показва, че отнема около 30% повече време за извършване на едновременна обработка и трябва да използвам последователна обработка тук. Предполагам, че това е така, защото заключването на базата данни води до това, че режийните разходи за ConcurrentQueue
са по-скъпи от печалбите от паралелната обработка. Правилно ли е това заключение и има ли нещо, което мога да направя, за да ускоря обработкатаили съм останал като за Datatable
„Трябва да синхронизирате всички операции за запис“.
Ето моя тестов код, който може да не е научно точен. Ето таймера и разговорите между тях.
dbTimer.Restart();
Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRow = InsertToPlayerQ(addUpdatePlayers);
Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRow = InsertToPlayerStatQ(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRow, addPlayerStatRow);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds single threaded", dbTimer.Elapsed.TotalSeconds);
dbTimer.Restart();
ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows = InsertToPlayerQueue(addUpdatePlayers);
ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows = InsertToPlayerStatQueue(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRows, addPlayerStatRows);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds concurrently", dbTimer.Elapsed.TotalSeconds);
И в двата примера добавям към Queue
и ConcurrentQueue
по идентичен начин с една резба. Единствената разлика е вмъкването в таблицата с данни. Подходът с една нишка вмъква следното:
private static void UpdatePlayerStatsInDB(Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
try
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.Count > 0)
{
row = addPlayerRows.Dequeue();
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
}
try
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.Count > 0)
{
row = addPlayerStatRows.Dequeue();
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
}
catch (Exception)
{
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
Паралелният добавя, както следва
private static void UpdatePlayerStatsInDB(ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
Action actionPlayer = () =>
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.TryDequeue(out row))
{
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
};
Action actionPlayerStat = () =>
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.TryDequeue(out row))
{
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
};
Action[] actions = new Action[Environment.ProcessorCount * 2];
for (int i = 0; i < Environment.ProcessorCount; i++)
{
actions[i * 2] = actionPlayer;
actions[i * 2 + 1] = actionPlayerStat;
}
try
{
// Start ProcessorCount concurrent consuming actions.
Parallel.Invoke(actions);
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
Разликата във времето е 4,6 секунди за еднонишковия и 6,1 за паралелния. Invoke.
ConcurrentQueue
и щях да се опитам да използвам множество нишки, за да се опитам да ускоря транзакциите на базата данни, но след това се чудех дали да използвам просто обикновена опашка и една нишка - person Harrison   schedule 27.09.2013AddPlayerStatsRow
). Най-простият начин да го направите е BlockingCollection, подкрепена отConcurrentQueue
работещ в негова собствена нишка (просто извикватеGetConsumingEnumerable()
в foreach цикъл и просто го оставяте да работи завинаги). По-сложните решения могат да включват писане на собствено разширение наSynchronizationContext
. - person Scott Chamberlain   schedule 27.09.2013