AyCode.Core/BenchmarkSuite1/SerializationBenchmarks.cs

516 lines
23 KiB
C#

using AyCode.Core.Extensions;
using AyCode.Core.Interfaces;
using BenchmarkDotNet.Attributes;
using Newtonsoft.Json;
using System.Text;
namespace AyCode.Core.Benchmarks;
[MemoryDiagnoser]
public class SerializationBenchmarks
{
// Complex graph with 7 levels, ~1500 objects, cross-references
private Level1_Company _complexGraph = null!;
// Pre-serialized JSON for deserialization benchmarks
private string _newtonsoftJson = null!;
private string _ayCodeJson = null!;
// Settings
private JsonSerializerSettings _newtonsoftNoRefSettings = null!;
private JsonSerializerSettings _ayCodeSettings = null!;
[GlobalSetup]
public void Setup()
{
// Newtonsoft WITHOUT reference handling (baseline)
_newtonsoftNoRefSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore, // Fair comparison - also skip defaults
Formatting = Formatting.None
};
// AyCode WITH reference handling (our optimized solution)
_ayCodeSettings = SerializeObjectExtensions.Options;
// Create complex 7-level graph with ~1500 objects and cross-references
_complexGraph = CreateComplexGraph();
// Pre-serialize for deserialization benchmarks
_newtonsoftJson = JsonConvert.SerializeObject(_complexGraph, _newtonsoftNoRefSettings);
_ayCodeJson = _complexGraph.ToJson(_ayCodeSettings);
// Output sizes for comparison
var newtonsoftBytes = Encoding.UTF8.GetByteCount(_newtonsoftJson);
var ayCodeBytes = Encoding.UTF8.GetByteCount(_ayCodeJson);
Console.WriteLine("=== JSON Size Comparison ===");
Console.WriteLine($"Newtonsoft (skip defaults): {_newtonsoftJson.Length:N0} chars ({newtonsoftBytes:N0} bytes, {newtonsoftBytes / 1024.0:F1} KB)");
Console.WriteLine($"AcJsonSerializer (refs+skip): {_ayCodeJson.Length:N0} chars ({ayCodeBytes:N0} bytes, {ayCodeBytes / 1024.0:F1} KB)");
Console.WriteLine($"Size reduction: {(1.0 - (double)ayCodeBytes / newtonsoftBytes) * 100:F1}%");
Console.WriteLine($"Bytes saved: {newtonsoftBytes - ayCodeBytes:N0}");
}
#region Serialization Benchmarks
[Benchmark(Description = "Newtonsoft (no refs)")]
[BenchmarkCategory("Serialize")]
public string Serialize_Newtonsoft_NoRefs()
=> JsonConvert.SerializeObject(_complexGraph, _newtonsoftNoRefSettings);
[Benchmark(Description = "AyCode (with refs)")]
[BenchmarkCategory("Serialize")]
public string Serialize_AyCode_WithRefs()
=> _complexGraph.ToJson(_ayCodeSettings);
#endregion
#region Deserialization Benchmarks
[Benchmark(Description = "Newtonsoft (no refs)")]
[BenchmarkCategory("Deserialize")]
public Level1_Company? Deserialize_Newtonsoft_NoRefs()
=> JsonConvert.DeserializeObject<Level1_Company>(_newtonsoftJson, _newtonsoftNoRefSettings);
[Benchmark(Description = "AyCode (with refs)")]
[BenchmarkCategory("Deserialize")]
public Level1_Company? Deserialize_AyCode_WithRefs()
=> _ayCodeJson.JsonTo<Level1_Company>(_ayCodeSettings);
[Benchmark(Description = "AcJsonDeserializer (custom)")]
[BenchmarkCategory("Deserialize")]
public Level1_Company? Deserialize_AcJsonDeserializer()
=> AcJsonDeserializer.Deserialize<Level1_Company>(_ayCodeJson);
#endregion
#region JSON Size Comparison (not timed, just for reporting)
[Benchmark(Description = "JSON Size - Newtonsoft")]
[BenchmarkCategory("Size")]
public int JsonSize_Newtonsoft() => _newtonsoftJson.Length;
[Benchmark(Description = "JSON Size - AyCode")]
[BenchmarkCategory("Size")]
public int JsonSize_AyCode() => _ayCodeJson.Length;
#endregion
#region Complex Graph Factory - 7 Levels, ~1500 objects, Cross-references
private static int _idCounter = 1;
/// <summary>
/// Creates a 7-level deep graph with approximately 1500 objects and cross-references.
/// Structure: Company -> Departments -> Teams -> Projects -> Tasks -> SubTasks -> Comments
/// Each object has 8-15 properties of various types.
/// </summary>
private static Level1_Company CreateComplexGraph()
{
_idCounter = 1;
// Shared references (cross-references across the graph)
var sharedTags = Enumerable.Range(1, 10)
.Select(i => new SharedTag
{
Id = _idCounter++,
Name = $"Tag-{i}",
Color = $"#{i:X2}{i * 10:X2}{i * 20:X2}",
Priority = i % 5,
IsActive = i % 2 == 0,
CreatedAt = DateTime.UtcNow.AddDays(-i * 10),
Metadata = $"Metadata for tag {i}"
})
.ToList();
var sharedCategories = Enumerable.Range(1, 5)
.Select(i => new SharedCategory
{
Id = _idCounter++,
Name = $"Category-{i}",
Description = $"Description for category {i} with some extra text to make it longer",
SortOrder = i * 100,
IconUrl = $"https://icons.example.com/cat-{i}.png",
IsDefault = i == 1,
ParentCategoryId = i > 1 ? i - 1 : null,
CreatedAt = DateTime.UtcNow.AddMonths(-i),
UpdatedAt = DateTime.UtcNow.AddDays(-i)
})
.ToList();
var sharedUser = new SharedUser
{
Id = _idCounter++,
Username = "admin",
Email = "admin@company.com",
FirstName = "System",
LastName = "Administrator",
PhoneNumber = "+1-555-0100",
IsActive = true,
Role = UserRole.Admin,
LastLoginAt = DateTime.UtcNow.AddHours(-1),
CreatedAt = DateTime.UtcNow.AddYears(-2),
Preferences = new UserPreferences
{
Theme = "dark",
Language = "en-US",
NotificationsEnabled = true,
EmailDigestFrequency = "daily"
}
};
// Level 1: Company (1 object)
var company = new Level1_Company
{
Id = _idCounter++,
Name = "TechCorp International",
LegalName = "TechCorp International Holdings Ltd.",
TaxId = "TC-123456789",
FoundedDate = new DateTime(2010, 3, 15),
EmployeeCount = 1500,
AnnualRevenue = 125_000_000.50m,
IsPubliclyTraded = true,
StockSymbol = "TECH",
HeadquartersAddress = "123 Innovation Drive, Tech City, TC 12345",
Website = "https://www.techcorp.example.com",
PrimaryContact = sharedUser,
MainCategory = sharedCategories[0],
Tags = [sharedTags[0], sharedTags[1], sharedTags[2]],
CreatedAt = DateTime.UtcNow.AddYears(-5),
UpdatedAt = DateTime.UtcNow
};
// Level 2: Departments (5 objects)
company.Departments = Enumerable.Range(1, 5).Select(deptIdx => new Level2_Department
{
Id = _idCounter++,
Name = $"Department-{deptIdx}",
Code = $"DEPT-{deptIdx:D3}",
Description = $"This is department {deptIdx} responsible for various operations and strategic initiatives",
Budget = 1_000_000m + (deptIdx * 250_000m),
HeadCount = 50 + (deptIdx * 20),
Location = $"Building {(char)('A' + deptIdx - 1)}, Floor {deptIdx}",
CostCenter = $"CC-{1000 + deptIdx}",
IsActive = true,
Manager = sharedUser, // Cross-reference
Category = sharedCategories[deptIdx % sharedCategories.Count], // Cross-reference
Tags = [sharedTags[deptIdx % sharedTags.Count], sharedTags[(deptIdx + 1) % sharedTags.Count]], // Cross-reference
EstablishedDate = DateTime.UtcNow.AddYears(-4).AddMonths(deptIdx),
CreatedAt = DateTime.UtcNow.AddYears(-4),
UpdatedAt = DateTime.UtcNow.AddMonths(-deptIdx),
// Level 3: Teams (6 per department = 30 total)
Teams = Enumerable.Range(1, 6).Select(teamIdx => new Level3_Team
{
Id = _idCounter++,
Name = $"Team-{deptIdx}-{teamIdx}",
Acronym = $"T{deptIdx}{teamIdx}",
Description = $"Team {teamIdx} in department {deptIdx}, focused on delivering excellence",
MemberCount = 5 + (teamIdx * 2),
Capacity = 10 + (teamIdx * 2),
Utilization = 0.65 + (teamIdx * 0.05),
SprintLength = 14,
VelocityAverage = 42.5 + teamIdx,
IsRemote = teamIdx % 3 == 0,
Timezone = teamIdx % 2 == 0 ? "UTC" : "America/New_York",
SlackChannel = $"#team-{deptIdx}-{teamIdx}",
TeamLead = sharedUser, // Cross-reference
PrimaryTag = sharedTags[(deptIdx + teamIdx) % sharedTags.Count], // Cross-reference
CreatedAt = DateTime.UtcNow.AddYears(-3).AddMonths(teamIdx),
UpdatedAt = DateTime.UtcNow.AddDays(-teamIdx * 7),
// Level 4: Projects (4 per team = 120 total)
Projects = Enumerable.Range(1, 4).Select(projIdx => new Level4_Project
{
Id = _idCounter++,
Name = $"Project-{deptIdx}-{teamIdx}-{projIdx}",
Code = $"PRJ-{deptIdx}{teamIdx}{projIdx:D2}",
Description = $"Project {projIdx} for team {teamIdx}, delivering key business value and innovation",
Status = (ProjectStatus)(projIdx % 4),
Priority = (Priority)(projIdx % 3),
Budget = 50_000m + (projIdx * 15_000m),
SpentAmount = 25_000m + (projIdx * 5_000m),
ProgressPercent = 0.1 + (projIdx * 0.2),
StartDate = DateTime.UtcNow.AddMonths(-projIdx * 2),
DueDate = DateTime.UtcNow.AddMonths(projIdx),
CompletedDate = projIdx == 4 ? DateTime.UtcNow.AddDays(-10) : null,
EstimatedHours = 200 + (projIdx * 50),
ActualHours = 150 + (projIdx * 40),
RiskLevel = projIdx % 3,
Owner = sharedUser, // Cross-reference
Category = sharedCategories[projIdx % sharedCategories.Count], // Cross-reference
Tags = [sharedTags[projIdx % sharedTags.Count]], // Cross-reference
CreatedAt = DateTime.UtcNow.AddMonths(-projIdx * 3),
UpdatedAt = DateTime.UtcNow.AddDays(-projIdx),
// Level 5: Tasks (5 per project = 600 total)
Tasks = Enumerable.Range(1, 5).Select(taskIdx => new Level5_Task
{
Id = _idCounter++,
Title = $"Task-{deptIdx}-{teamIdx}-{projIdx}-{taskIdx}",
Description = $"Detailed task description for task {taskIdx} in project {projIdx}. This includes requirements and acceptance criteria.",
Status = (TaskStatus)(taskIdx % 5),
Priority = (Priority)(taskIdx % 3),
Type = (TaskType)(taskIdx % 4),
StoryPoints = taskIdx * 2,
EstimatedHours = 4 + taskIdx * 2,
ActualHours = 3 + taskIdx * 1.5,
DueDate = DateTime.UtcNow.AddDays(taskIdx * 3),
CompletedDate = taskIdx <= 2 ? DateTime.UtcNow.AddDays(-taskIdx) : null,
IsBlocked = taskIdx == 3,
BlockedReason = taskIdx == 3 ? "Waiting for external dependency" : null,
Assignee = sharedUser, // Cross-reference
Reporter = sharedUser, // Cross-reference
Labels = [sharedTags[taskIdx % sharedTags.Count]], // Cross-reference
CreatedAt = DateTime.UtcNow.AddDays(-taskIdx * 5),
UpdatedAt = DateTime.UtcNow.AddHours(-taskIdx),
// Level 6: SubTasks (3 per task = 1800 total -> we'll limit to keep ~1500)
SubTasks = Enumerable.Range(1, 2).Select(subIdx => new Level6_SubTask
{
Id = _idCounter++,
Title = $"SubTask-{taskIdx}-{subIdx}",
Description = $"Sub-task {subIdx} details for completing parent task {taskIdx}",
Status = (TaskStatus)(subIdx % 5),
EstimatedMinutes = 30 + subIdx * 15,
ActualMinutes = 25 + subIdx * 12,
IsCompleted = subIdx == 1,
CompletedAt = subIdx == 1 ? DateTime.UtcNow.AddHours(-subIdx * 2) : null,
Assignee = sharedUser, // Cross-reference
CreatedAt = DateTime.UtcNow.AddDays(-subIdx),
UpdatedAt = DateTime.UtcNow.AddMinutes(-subIdx * 30),
// Level 7: Comments (2 per subtask = 2400 total -> limiting)
Comments = Enumerable.Range(1, 1).Select(comIdx => new Level7_Comment
{
Id = _idCounter++,
Text = $"Comment {comIdx} on subtask {subIdx}: This is a detailed comment with feedback and suggestions for improvement.",
Author = sharedUser, // Cross-reference
IsEdited = comIdx % 2 == 0,
EditedAt = comIdx % 2 == 0 ? DateTime.UtcNow.AddHours(-1) : null,
LikeCount = comIdx * 3,
ReplyCount = comIdx,
CreatedAt = DateTime.UtcNow.AddHours(-comIdx * 4),
MentionedTags = [sharedTags[comIdx % sharedTags.Count]] // Cross-reference
}).ToList()
}).ToList()
}).ToList()
}).ToList()
}).ToList()
}).ToList();
return company;
}
#endregion
#region 7-Level Deep DTOs with 8-15 Properties Each
// Shared cross-reference types
public class SharedTag : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Color { get; set; } = "";
public int Priority { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public string Metadata { get; set; } = "";
}
public class SharedCategory : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public int SortOrder { get; set; }
public string IconUrl { get; set; } = "";
public bool IsDefault { get; set; }
public int? ParentCategoryId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class SharedUser : IId<int>
{
public int Id { get; set; }
public string Username { get; set; } = "";
public string Email { get; set; } = "";
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public string PhoneNumber { get; set; } = "";
public bool IsActive { get; set; }
public UserRole Role { get; set; }
public DateTime? LastLoginAt { get; set; }
public DateTime CreatedAt { get; set; }
public UserPreferences? Preferences { get; set; }
}
public class UserPreferences
{
public string Theme { get; set; } = "";
public string Language { get; set; } = "";
public bool NotificationsEnabled { get; set; }
public string EmailDigestFrequency { get; set; } = "";
}
public enum UserRole { User, Manager, Admin }
public enum ProjectStatus { Planning, Active, OnHold, Completed }
public enum TaskStatus { Backlog, Todo, InProgress, Review, Done }
public enum TaskType { Feature, Bug, Improvement, Task }
public enum Priority { Low, Medium, High }
// Level 1: Company (15 properties)
public class Level1_Company : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string LegalName { get; set; } = "";
public string TaxId { get; set; } = "";
public DateTime FoundedDate { get; set; }
public int EmployeeCount { get; set; }
public decimal AnnualRevenue { get; set; }
public bool IsPubliclyTraded { get; set; }
public string? StockSymbol { get; set; }
public string HeadquartersAddress { get; set; } = "";
public string Website { get; set; } = "";
public SharedUser? PrimaryContact { get; set; } // Cross-ref
public SharedCategory? MainCategory { get; set; } // Cross-ref
public List<SharedTag> Tags { get; set; } = []; // Cross-ref
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level2_Department> Departments { get; set; } = [];
}
// Level 2: Department (15 properties)
public class Level2_Department : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Code { get; set; } = "";
public string Description { get; set; } = "";
public decimal Budget { get; set; }
public int HeadCount { get; set; }
public string Location { get; set; } = "";
public string CostCenter { get; set; } = "";
public bool IsActive { get; set; }
public SharedUser? Manager { get; set; } // Cross-ref
public SharedCategory? Category { get; set; } // Cross-ref
public List<SharedTag> Tags { get; set; } = []; // Cross-ref
public DateTime EstablishedDate { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level3_Team> Teams { get; set; } = [];
}
// Level 3: Team (15 properties)
public class Level3_Team : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Acronym { get; set; } = "";
public string Description { get; set; } = "";
public int MemberCount { get; set; }
public int Capacity { get; set; }
public double Utilization { get; set; }
public int SprintLength { get; set; }
public double VelocityAverage { get; set; }
public bool IsRemote { get; set; }
public string Timezone { get; set; } = "";
public string SlackChannel { get; set; } = "";
public SharedUser? TeamLead { get; set; } // Cross-ref
public SharedTag? PrimaryTag { get; set; } // Cross-ref
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level4_Project> Projects { get; set; } = [];
}
// Level 4: Project (18 properties)
public class Level4_Project : IId<int>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Code { get; set; } = "";
public string Description { get; set; } = "";
public ProjectStatus Status { get; set; }
public Priority Priority { get; set; }
public decimal Budget { get; set; }
public decimal SpentAmount { get; set; }
public double ProgressPercent { get; set; }
public DateTime StartDate { get; set; }
public DateTime DueDate { get; set; }
public DateTime? CompletedDate { get; set; }
public int EstimatedHours { get; set; }
public int ActualHours { get; set; }
public int RiskLevel { get; set; }
public SharedUser? Owner { get; set; } // Cross-ref
public SharedCategory? Category { get; set; } // Cross-ref
public List<SharedTag> Tags { get; set; } = []; // Cross-ref
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level5_Task> Tasks { get; set; } = [];
}
// Level 5: Task (18 properties)
public class Level5_Task : IId<int>
{
public int Id { get; set; }
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public TaskStatus Status { get; set; }
public Priority Priority { get; set; }
public TaskType Type { get; set; }
public int StoryPoints { get; set; }
public double EstimatedHours { get; set; }
public double ActualHours { get; set; }
public DateTime DueDate { get; set; }
public DateTime? CompletedDate { get; set; }
public bool IsBlocked { get; set; }
public string? BlockedReason { get; set; }
public SharedUser? Assignee { get; set; } // Cross-ref
public SharedUser? Reporter { get; set; } // Cross-ref
public List<SharedTag> Labels { get; set; } = []; // Cross-ref
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level6_SubTask> SubTasks { get; set; } = [];
}
// Level 6: SubTask (11 properties)
public class Level6_SubTask : IId<int>
{
public int Id { get; set; }
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public TaskStatus Status { get; set; }
public int EstimatedMinutes { get; set; }
public int ActualMinutes { get; set; }
public bool IsCompleted { get; set; }
public DateTime? CompletedAt { get; set; }
public SharedUser? Assignee { get; set; } // Cross-ref
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public List<Level7_Comment> Comments { get; set; } = [];
}
// Level 7: Comment (10 properties)
public class Level7_Comment : IId<int>
{
public int Id { get; set; }
public string Text { get; set; } = "";
public SharedUser? Author { get; set; } // Cross-ref
public bool IsEdited { get; set; }
public DateTime? EditedAt { get; set; }
public int LikeCount { get; set; }
public int ReplyCount { get; set; }
public DateTime CreatedAt { get; set; }
public List<SharedTag> MentionedTags { get; set; } = []; // Cross-ref
}
#endregion
#region AcJsonSerializer Benchmarks
[Benchmark(Description = "AcJsonSerializer (custom)")]
[BenchmarkCategory("Serialize")]
public string Serialize_AcJsonSerializer()
=> AcJsonSerializer.Serialize(_complexGraph);
#endregion
}