C#/SQLite 性能与 FoxPro 导入文件
C#/SQLite performance vs FoxPro for importing files
我 "inherited" 一些用于处理大型文本文件的遗留软件,目前是用 Visual Foxpro(特别是第 7 版)编写的。
我正在探索用更易于修改和维护的新程序替换这些程序的选项。但是,我无法接近 Foxpro 导入大型文本文件的性能。
例如,我有一个约 1gb 的文本文件,其中包含约 110,000 条记录,每条记录有 213 个字段(每行长 9247 个字符),我正在对其进行测试。
使用 Foxpro:
? SECONDS()
USE new
APPEND FROM test.dat TYPE sdf
USE
? SECONDS()
在我的电脑上导入数据库只用了不到 10 秒。
使用 C#/SQLite(使用 Filehelpers 和 System.Data.SQLite),我能够完成此导入的最快时间超过一分钟。我已尝试使用 this question 的建议(例如交易等)尽可能优化它。实际上,如果我不将其与 Foxpro 的 10 秒进行比较,导入 1gb 文件的一分钟似乎并不坏。
大约 1 分钟内花费的时间细分为:
Filehelpers reading file and parsing: 12 seconds.
Building SQL commands from Filehelpers object: 15 seconds.
Running individual ExecuteNonQuery()'s: 24 seconds.
Committing transactions (every 5000 records): 12 seconds.
与线程链接相比,我每秒的插入速度要慢得多,但我的记录有 213 个字段,而 7 个字段,所以这是预料之中的。如果我按 fields/second 将其分解,我大约有 360,000 与线程的 630,000。其他海报的兆字节插入率是 ~2.24 megabyte/s,我是 15.4 megatbyte/s。因此,我认为我的表现与其他海报相当,我可能没有更多的优化可以做。
为什么这比 Foxpro 导入慢很多(慢 5-6 倍)?这是否只是苹果对橙子,我应该接受较慢的速度来权衡我使用新技术获得的其他好处?
编辑:
这是我 运行 的一些测试代码,随机数据显示了相似的速度....我是否正确地执行了交易? ExecuteNonQuery() 花费了大量时间。我已经用简单的、非参数化的、很可能是低效的字符串操作实现了所有 SQL 命令,但在这一点上我并不过分关心(我将从文件中读取,这种方法要快得多比使用 entity framework).
public class Program
{
public static void Main(string[] args)
{
TestSqlite(100, 20000);
}
public static void TestSqlite(int numColumns, int numRows)
{
Console.WriteLine("Starting Import.... columns: " + numColumns.ToString() + " rows: " + numRows.ToString());
var conn = new SQLiteConnection(@"Data Source=C:\vsdev\SqliteTest\src\SqliteTest\ddic.db;Version=3");
conn.Open();
var cmd = new SQLiteCommand(conn);
cmd.CommandText = "DROP TABLE IF EXISTS test";
cmd.ExecuteNonQuery();
string createCmd = "CREATE TABLE 'test'('testId' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ";
for (var i = 0; i < numColumns; i++)
{
createCmd += "'test" + i.ToString() + "' TEXT, ";
}
createCmd = createCmd.Substring(0, createCmd.Length - 2);
createCmd += ")";
cmd.CommandText = createCmd;
cmd.ExecuteNonQuery();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var transaction = conn.BeginTransaction();
int lineCount = 0;
long startTime;
long endTime;
long totalStringTime = 0;
long totalAddTime = 0;
long totalSaveTime = 0;
string command;
for (var l = 0; l < numRows; l++)
{
startTime = stopWatch.ElapsedMilliseconds;
command = CreateRandomInsert("test", numColumns);
endTime = stopWatch.ElapsedMilliseconds;
totalStringTime += endTime - startTime;
///Execute Query
startTime = stopWatch.ElapsedMilliseconds;
cmd.CommandText = command;
cmd.ExecuteNonQuery();
endTime = stopWatch.ElapsedMilliseconds;
totalAddTime += endTime - startTime;
if (lineCount > 5000)
{
lineCount = 0;
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
transaction = conn.BeginTransaction();
cmd = new SQLiteCommand(conn);
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.Write('.');
}
lineCount += 1;
}
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.WriteLine('.');
Console.WriteLine("String time: " + totalStringTime.ToString());
Console.WriteLine("ExecuteNonQuery time: " + totalAddTime.ToString() + ", per 1000 records: " + (totalAddTime / (numRows / 1000)).ToString());
Console.WriteLine("Commit time: " + totalSaveTime.ToString() + ", per 1000 records: " + (totalSaveTime / (numRows / 1000)).ToString());
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine(" in " + elapsedTime);
conn.Close();
}
public static string CreateRandomInsert(string TableName, int numColumns)
{
List<string> nameList = new List<string>();
List<string> valueList = new List<string>();
for (var i = 0; i < numColumns; i++)
{
nameList.Add("test" + i.ToString());
valueList.Add(Guid.NewGuid().ToString());
}
return CreateSql(TableName, nameList, valueList);
}
public static string CreateSql(string TableName, List<string> names, List<string> values)
{
string textCommand = "";
textCommand += "INSERT INTO " + TableName + " (";
foreach (var nameVal in names)
{
textCommand += nameVal + ", ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ") VALUES (";
foreach (var val in values)
{
textCommand += "'" + val + "', ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ");";
return textCommand;
}
}
当是固定宽度的数据时,VFP比其他数据库更有优势。低级别,它几乎与 VFP 存储其数据的格式相同。例如,如果您没有任何与 Foxpro 2.x 不兼容的字段,那么导入该数据将仅意味着在每行前面添加一个 0x20 字节(如果尚未写入文件头,则写入文件头) - 并更新索引(如果有的话),这将是真正耗时的部分。如果您需要解析行,那么与 C# 相比,VFP 在字符串操作上非常慢。
(我不知道 FileHelpers,感谢指点)
9247 字节的固定数据和 213 个字段很有趣。我想测试插入 SQLite、postgreSQL、SQL 服务器和一些 NoSQL 数据库。您能否共享一个空的 dbf(或者只是作为代码的结构或 xml - 可能包含来自文件的 1-2 行样本数据)?我之前没有尝试过 200 多个字段,否则 110K 行的 1 分钟听起来很慢。
VFP table 只能附加此类数据 2 次,您需要创建另一个 table。你说的是 "manipulation" 文本文件,但你没有评论那是什么类型的操作。也许不附加到 table 的直接 "manipulation" 更高效、更快并且整个过程完成得更快?只是一个想法。
我 "inherited" 一些用于处理大型文本文件的遗留软件,目前是用 Visual Foxpro(特别是第 7 版)编写的。
我正在探索用更易于修改和维护的新程序替换这些程序的选项。但是,我无法接近 Foxpro 导入大型文本文件的性能。
例如,我有一个约 1gb 的文本文件,其中包含约 110,000 条记录,每条记录有 213 个字段(每行长 9247 个字符),我正在对其进行测试。
使用 Foxpro:
? SECONDS()
USE new
APPEND FROM test.dat TYPE sdf
USE
? SECONDS()
在我的电脑上导入数据库只用了不到 10 秒。
使用 C#/SQLite(使用 Filehelpers 和 System.Data.SQLite),我能够完成此导入的最快时间超过一分钟。我已尝试使用 this question 的建议(例如交易等)尽可能优化它。实际上,如果我不将其与 Foxpro 的 10 秒进行比较,导入 1gb 文件的一分钟似乎并不坏。
大约 1 分钟内花费的时间细分为:
Filehelpers reading file and parsing: 12 seconds.
Building SQL commands from Filehelpers object: 15 seconds.
Running individual ExecuteNonQuery()'s: 24 seconds.
Committing transactions (every 5000 records): 12 seconds.
与线程链接相比,我每秒的插入速度要慢得多,但我的记录有 213 个字段,而 7 个字段,所以这是预料之中的。如果我按 fields/second 将其分解,我大约有 360,000 与线程的 630,000。其他海报的兆字节插入率是 ~2.24 megabyte/s,我是 15.4 megatbyte/s。因此,我认为我的表现与其他海报相当,我可能没有更多的优化可以做。
为什么这比 Foxpro 导入慢很多(慢 5-6 倍)?这是否只是苹果对橙子,我应该接受较慢的速度来权衡我使用新技术获得的其他好处?
编辑:
这是我 运行 的一些测试代码,随机数据显示了相似的速度....我是否正确地执行了交易? ExecuteNonQuery() 花费了大量时间。我已经用简单的、非参数化的、很可能是低效的字符串操作实现了所有 SQL 命令,但在这一点上我并不过分关心(我将从文件中读取,这种方法要快得多比使用 entity framework).
public class Program
{
public static void Main(string[] args)
{
TestSqlite(100, 20000);
}
public static void TestSqlite(int numColumns, int numRows)
{
Console.WriteLine("Starting Import.... columns: " + numColumns.ToString() + " rows: " + numRows.ToString());
var conn = new SQLiteConnection(@"Data Source=C:\vsdev\SqliteTest\src\SqliteTest\ddic.db;Version=3");
conn.Open();
var cmd = new SQLiteCommand(conn);
cmd.CommandText = "DROP TABLE IF EXISTS test";
cmd.ExecuteNonQuery();
string createCmd = "CREATE TABLE 'test'('testId' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ";
for (var i = 0; i < numColumns; i++)
{
createCmd += "'test" + i.ToString() + "' TEXT, ";
}
createCmd = createCmd.Substring(0, createCmd.Length - 2);
createCmd += ")";
cmd.CommandText = createCmd;
cmd.ExecuteNonQuery();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var transaction = conn.BeginTransaction();
int lineCount = 0;
long startTime;
long endTime;
long totalStringTime = 0;
long totalAddTime = 0;
long totalSaveTime = 0;
string command;
for (var l = 0; l < numRows; l++)
{
startTime = stopWatch.ElapsedMilliseconds;
command = CreateRandomInsert("test", numColumns);
endTime = stopWatch.ElapsedMilliseconds;
totalStringTime += endTime - startTime;
///Execute Query
startTime = stopWatch.ElapsedMilliseconds;
cmd.CommandText = command;
cmd.ExecuteNonQuery();
endTime = stopWatch.ElapsedMilliseconds;
totalAddTime += endTime - startTime;
if (lineCount > 5000)
{
lineCount = 0;
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
transaction = conn.BeginTransaction();
cmd = new SQLiteCommand(conn);
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.Write('.');
}
lineCount += 1;
}
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.WriteLine('.');
Console.WriteLine("String time: " + totalStringTime.ToString());
Console.WriteLine("ExecuteNonQuery time: " + totalAddTime.ToString() + ", per 1000 records: " + (totalAddTime / (numRows / 1000)).ToString());
Console.WriteLine("Commit time: " + totalSaveTime.ToString() + ", per 1000 records: " + (totalSaveTime / (numRows / 1000)).ToString());
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine(" in " + elapsedTime);
conn.Close();
}
public static string CreateRandomInsert(string TableName, int numColumns)
{
List<string> nameList = new List<string>();
List<string> valueList = new List<string>();
for (var i = 0; i < numColumns; i++)
{
nameList.Add("test" + i.ToString());
valueList.Add(Guid.NewGuid().ToString());
}
return CreateSql(TableName, nameList, valueList);
}
public static string CreateSql(string TableName, List<string> names, List<string> values)
{
string textCommand = "";
textCommand += "INSERT INTO " + TableName + " (";
foreach (var nameVal in names)
{
textCommand += nameVal + ", ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ") VALUES (";
foreach (var val in values)
{
textCommand += "'" + val + "', ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ");";
return textCommand;
}
}
当是固定宽度的数据时,VFP比其他数据库更有优势。低级别,它几乎与 VFP 存储其数据的格式相同。例如,如果您没有任何与 Foxpro 2.x 不兼容的字段,那么导入该数据将仅意味着在每行前面添加一个 0x20 字节(如果尚未写入文件头,则写入文件头) - 并更新索引(如果有的话),这将是真正耗时的部分。如果您需要解析行,那么与 C# 相比,VFP 在字符串操作上非常慢。
(我不知道 FileHelpers,感谢指点)
9247 字节的固定数据和 213 个字段很有趣。我想测试插入 SQLite、postgreSQL、SQL 服务器和一些 NoSQL 数据库。您能否共享一个空的 dbf(或者只是作为代码的结构或 xml - 可能包含来自文件的 1-2 行样本数据)?我之前没有尝试过 200 多个字段,否则 110K 行的 1 分钟听起来很慢。
VFP table 只能附加此类数据 2 次,您需要创建另一个 table。你说的是 "manipulation" 文本文件,但你没有评论那是什么类型的操作。也许不附加到 table 的直接 "manipulation" 更高效、更快并且整个过程完成得更快?只是一个想法。