我正在尋找一種方法來提高 C# 代碼的性能,并希望得到任何幫助。
有2個表:Table_1和Table_2,我想從Table_2中收集資料并保存在Table_1中,格式如下:
表格1
| 日期 | Some_Stat |
|---|---|
| 2021-06-01 | 23.7 |
| 2021-06-02 | 12.6 |
| 2021-06-03 | 47.9 |
表_2
| 日期 | ID | 一種 | 乙 | C |
|---|---|---|---|---|
| 2021-06-02 | 4 | 21 | 23 | 13 |
| 2021-06-02 | 3 | 67 | 31 | 25 |
| 2021-06-01 | 3 | 45 | 54 | 33 |
| 2021-06-03 | 3 | 71 | 28 | 51 |
| 2021-06-03 | 4 | 26 | 83 | 24 |
我的目標:加入后的Table_1
| 日期 | Some_Stat | A_3 | B_3 | C_3 | A_4 | B_4 | C_4 |
|---|---|---|---|---|---|---|---|
| 2021-06-01 | 23.7 | 45 | 54 | 33 | 0 | 0 | 0 |
| 2021-06-02 | 12.6 | 67 | 31 | 25 | 21 | 23 | 13 |
| 2021-06-03 | 47.9 | 71 | 28 | 51 | 26 | 83 | 24 |
為了實作這一點,我使用了下面的代碼,它可以作業,但速度很慢,尤其是當有數千個 ID 時。基本上,代碼所做的是首先進行所需的連接并將 LINQ 連接結果傳輸到現有的 Table_1(復制列)。我檢查了性能時間,LINQ 查詢總是非常快(時間:0 毫秒),但資料傳輸是問題所在。
List<int> ids = Table_2.AsEnumerable().Select(s => s.Field<int>("ID").Distinct().ToList();
for (int i = 0; ind < ids.Count; i )
{
Table_1.Columns.Add($"A_{ids[i]}", typeof(double));
Table_1.Columns.Add($"B_{ids[i]}", typeof(double));
Table_1.Columns.Add($"C_{ids[i]}", typeof(double));
}
for (int i = 0; ind < ids.Count; i )
{
// LINQ join (fast)
var joinedTables = from T1 in Table_1.AsEnumerable()
join T2 in Table_2.Select($"ID = {ids[i]}").AsEnumerable()
on (String)T1["Date"] equals (String)T2["Date"]
into T1_and_T2
from TT in T1_and_T2.DefaultIfEmpty()
select new
{
Date = (String)T1["Date"],
A = TT != null ? (double)TT["A"] : 0.0,
B = TT != null ? (double)TT["B"] : 0.0,
C = TT != null ? (double)TT["C"] : 0.0,
};
// data transfer (very slow)
for (int day = 0; day < joinedTables.Count(); day )
{
Table_1.Rows[day][$"A_{ids[i]}"] = joinedTables.ElementAt(day).A;
Table_1.Rows[day][$"B_{ids[i]}"] = joinedTables.ElementAt(day).B;
Table_1.Rows[day][$"C_{ids[i]}"] = joinedTables.ElementAt(day).C;
}
}
Also instead of the data transfer version above I have tried another way, but it is as slow as the previous:
int day = 0;
foreach( var row in joinedTables)
{
Table_1.Rows[day][$"A_{ids[i]}"] = row.A;
Table_1.Rows[day][$"B_{ids[i]}"] = row.B;
Table_1.Rows[day][$"C_{ids[i]}"] = row.C;
}
Note: I am also open to new approaches on how to collect data from Table_2 in Table_1. There could be a way to use build-in functions (written in C or C ) which will access Machine Code directly ( for example, a function which copies columns from one table to another table, like in python) in order to avoid looping through rows.
uj5u.com熱心網友回復:
我的建議是使用不同的方法。ADataTable不是一個特別快的物件,查找列來設定值很慢。由于您可以使用該方法快速添加行,因此創建DataTable要替換的新物件Table_1會快得多。DataRowCollection.Add()
使用 aDictionary進行轉換也Table_2可以實作更快的查找ElementAt。
var joinDict = (from T2 in Table_2.AsEnumerable()
select new {
Date = T2.Field<string>("Date"),
ID = T2.Field<int>("ID"),
A = T2.Field<double>("A"),
B = T2.Field<double>("B"),
C = T2.Field<double>("C"),
})
.ToDictionary(t2 => (t2.Date, t2.ID));
List<int> ids = Table_2.AsEnumerable().Select(s => s.Field<int>("ID")).Distinct().OrderBy(x => x).ToList();
var ans = Table_1.Clone();
for (int i = 0; i < ids.Count; i ) {
ans.Columns.Add($"A_{ids[i]}", typeof(double));
ans.Columns.Add($"B_{ids[i]}", typeof(double));
ans.Columns.Add($"C_{ids[i]}", typeof(double));
}
foreach (DataRow row in Table_1.Rows) {
var newRow = new List<object> { row.Field<string>("Date") };
foreach (var id in ids) {
if (joinDict.TryGetValue((row.Field<string>("Date"), id), out var t2))
newRow.AddRange(new object[] { t2.A, t2.B, t2.C });
else
newRow.AddRange(new object[] { 0.0, 0.0, 0.0 });
}
ans.Rows.Add(newRow.ToArray());
}
Table_1 = ans;
在 75% 的人口中進行 100 天和Table_1每天 500 行的測驗Table_2,我得到了大約 128 倍的加速。
uj5u.com熱心網友回復:
您可以使用ToLookupLINQ 運算子,并Lookup<DateTime, DataRow>從Table_2. 這個只讀結構將為IGrouping<DateTime, DataRow>每個唯一日期包含一個,并且每個分組將包含DataRow與該日期關聯的所有 s:
var lookup = Table_2.AsEnumerable().ToLookup(r => r.Field<DateTime>("Date"));
然后對于 的每一行,Table_1您將能夠快速找到 的所有相關行Table_2:
foreach (DataRow row1 in Table_1.Rows)
{
DateTime date = row1.Field<DateTime>("Date");
IEnumerable<DataRow> group = lookup[date];
if (group == null) continue;
foreach (DataRow row2 in group)
{
int id = row2.Field<int>("ID");
row1[$"A_{id}"] = row2.Field<double>("A");
row1[$"B_{id}"] = row2.Field<double>("B");
row1[$"C_{id}"] = row2.Field<double>("C");
}
}
更新:您的性能問題似乎與加入兩個DataTables 無關,而是與更新一個DataTable包含數百甚至數千個DataColumns 的極寬相關。顯然DataTables 沒有針對這樣的場景進行優化。更新 a 的任何列的復雜度DataRow是 O(n2),其中 N 是列的總數。為了克服這個問題,您可以DataRow通過ItemArray屬性匯出存盤在 a 中的所有值,操作這些值,最后通過同一個屬性將它們匯入回來。BeginLoadData使用和EndLoadData方法可以進一步提高性能。
Table_1.BeginLoadData();
foreach (DataRow row1 in Table_1.Rows)
{
DateTime date = row1.Field<DateTime>("Date");
IEnumerable<DataRow> group = lookup[date];
if (group == null) continue;
object[] values = row1.ItemArray; // Export the raw data
foreach (DataRow row2 in group)
{
int id = row2.Field<int>("ID");
values[Table_1.Columns[$"A_{id}"].Ordinal] = row2.Field<double>("A");
values[Table_1.Columns[$"B_{id}"].Ordinal] = row2.Field<double>("B");
values[Table_1.Columns[$"C_{id}"].Ordinal] = row2.Field<double>("C");
}
row1.ItemArray = values; // Import the updated data
}
Table_1.EndLoadData();
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/446627.html
