248 lines
9.9 KiB
Java
248 lines
9.9 KiB
Java
package unit;
|
||
import java.util.*;
|
||
|
||
/**
|
||
* UnrealircdTest — CWE-407 benchmark for unrealircd-0001
|
||
*
|
||
* Models has_common_channels(c1, c2):
|
||
* SLOW: O(c1_channels × c2_channels) — IsMember = linked-list scan per channel
|
||
* FAST: O(c1_channels + c2_channels) — pre-build HashSet of c2's channels
|
||
*
|
||
* Also models the WHO global scan: O(U × c1 × c2) vs O(U × (c1+c2))
|
||
*/
|
||
public class UnrealircdTest {
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Data model
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class Channel {
|
||
final int id;
|
||
Channel(int id) { this.id = id; }
|
||
}
|
||
|
||
static class Client {
|
||
final String nick;
|
||
final List<Channel> channels = new ArrayList<>();
|
||
Client(String nick) { this.nick = nick; }
|
||
void join(Channel c) { channels.add(c); }
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// SLOW: IsMember = find_membership_link = O(n) list scan
|
||
// -------------------------------------------------------------------------
|
||
|
||
/** Returns ops count (number of channel comparisons performed) */
|
||
static long hasCommonChannels_slow(Client c1, Client c2) {
|
||
long ops = 0;
|
||
for (Channel ch1 : c1.channels) {
|
||
// IsMember(c2, ch1) = find_membership_link = O(c2.channels)
|
||
for (Channel ch2 : c2.channels) {
|
||
ops++;
|
||
if (ch2 == ch1) break;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* WHO global scan: for each user in server, call hasCommonChannels_slow.
|
||
* Returns total ops across all users.
|
||
*/
|
||
static long whoGlobalScan_slow(List<Client> users, Client requester) {
|
||
long totalOps = 0;
|
||
for (Client target : users) {
|
||
if (target == requester) continue;
|
||
totalOps += hasCommonChannels_slow(requester, target);
|
||
}
|
||
return totalOps;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// FAST: pre-build HashSet of c2's channels → O(1) membership test
|
||
// -------------------------------------------------------------------------
|
||
|
||
static long hasCommonChannels_fast(Client c1, Client c2) {
|
||
long ops = 0;
|
||
// Build O(c2) set
|
||
Set<Channel> c2set = new HashSet<>(c2.channels.size() * 2);
|
||
for (Channel ch : c2.channels) {
|
||
ops++;
|
||
c2set.add(ch);
|
||
}
|
||
// O(c1) probe with O(1) set membership
|
||
for (Channel ch : c1.channels) {
|
||
ops++;
|
||
if (c2set.contains(ch)) break; // found, same as early-exit in real code
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static long whoGlobalScan_fast(List<Client> users, Client requester) {
|
||
long totalOps = 0;
|
||
// Pre-build requester's channel set once
|
||
Set<Channel> reqSet = new HashSet<>(requester.channels.size() * 2);
|
||
for (Channel ch : requester.channels) reqSet.add(ch);
|
||
for (Client target : users) {
|
||
if (target == requester) continue;
|
||
long ops = reqSet.size(); // "build" cost amortised — count as 1 per target
|
||
for (Channel ch : target.channels) {
|
||
ops++;
|
||
if (reqSet.contains(ch)) break;
|
||
}
|
||
totalOps += ops;
|
||
}
|
||
return totalOps;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Benchmark harness
|
||
// -------------------------------------------------------------------------
|
||
|
||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||
slow.run(); fast.run();
|
||
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
|
||
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
|
||
double r = fOps > 0 ? (double) sOps / fOps : 0;
|
||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||
label, sMs, sOps, fMs, fOps, r);
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Setup helpers
|
||
// -------------------------------------------------------------------------
|
||
|
||
static List<Channel> makeChannels(int n) {
|
||
List<Channel> list = new ArrayList<>(n);
|
||
for (int i = 0; i < n; i++) list.add(new Channel(i));
|
||
return list;
|
||
}
|
||
|
||
static Client makeClient(String nick, List<Channel> allChannels, int count, int offset) {
|
||
Client c = new Client(nick);
|
||
for (int i = 0; i < count; i++) c.join(allChannels.get((offset + i) % allChannels.size()));
|
||
return c;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Main
|
||
// -------------------------------------------------------------------------
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("unrealircd-0001: has_common_channels CWE-407 benchmark");
|
||
System.out.println("=======================================================");
|
||
|
||
// --- Scenario 1: single has_common_channels call, C=50 channels/user ---
|
||
{
|
||
int C = 50;
|
||
List<Channel> allChans = makeChannels(200);
|
||
// c1 and c2 share ~half their channels (worst case: no early exit until middle)
|
||
Client c1 = makeClient("alice", allChans, C, 0);
|
||
Client c2 = makeClient("bob", allChans, C, C / 2);
|
||
|
||
long[] sOps = {0}, fOps = {0};
|
||
Runnable slow = () -> {
|
||
long ops = 0;
|
||
for (int i = 0; i < 10_000; i++) ops += hasCommonChannels_slow(c1, c2);
|
||
sOps[0] = ops;
|
||
};
|
||
Runnable fast = () -> {
|
||
long ops = 0;
|
||
for (int i = 0; i < 10_000; i++) ops += hasCommonChannels_fast(c1, c2);
|
||
fOps[0] = ops;
|
||
};
|
||
slow.run(); fast.run();
|
||
bench("has_common_channels C=50 (10k calls)", slow, fast, sOps[0], fOps[0]);
|
||
assert sOps[0] > fOps[0] * 5 :
|
||
"Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0];
|
||
}
|
||
|
||
// --- Scenario 2: WHO global scan, U=500 users, C=30 channels/user ---
|
||
{
|
||
int U = 500, C = 30;
|
||
List<Channel> allChans = makeChannels(300);
|
||
List<Client> users = new ArrayList<>(U);
|
||
for (int i = 0; i < U; i++)
|
||
users.add(makeClient("user" + i, allChans, C, i));
|
||
Client requester = users.get(0);
|
||
|
||
long[] sOps = {0}, fOps = {0};
|
||
Runnable slow = () -> sOps[0] = whoGlobalScan_slow(users, requester);
|
||
Runnable fast = () -> fOps[0] = whoGlobalScan_fast(users, requester);
|
||
slow.run(); fast.run();
|
||
bench("who_global_scan U=500 C=30", slow, fast, sOps[0], fOps[0]);
|
||
assert sOps[0] > fOps[0] * 3 :
|
||
"Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0];
|
||
}
|
||
|
||
// --- Scenario 3: high channel density, C=100 channels/user ---
|
||
{
|
||
int C = 100;
|
||
List<Channel> allChans = makeChannels(500);
|
||
Client c1 = makeClient("heavyuser1", allChans, C, 0);
|
||
Client c2 = makeClient("heavyuser2", allChans, C, 50);
|
||
|
||
long[] sOps = {0}, fOps = {0};
|
||
Runnable slow = () -> {
|
||
long ops = 0;
|
||
for (int i = 0; i < 5_000; i++) ops += hasCommonChannels_slow(c1, c2);
|
||
sOps[0] = ops;
|
||
};
|
||
Runnable fast = () -> {
|
||
long ops = 0;
|
||
for (int i = 0; i < 5_000; i++) ops += hasCommonChannels_fast(c1, c2);
|
||
fOps[0] = ops;
|
||
};
|
||
slow.run(); fast.run();
|
||
bench("has_common_channels C=100 (5k calls)", slow, fast, sOps[0], fOps[0]);
|
||
assert sOps[0] > fOps[0] * 10 :
|
||
"Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0];
|
||
}
|
||
|
||
// --- Scenario 4: unrealircd-0002 SJOIN member loop, M=500 members, C=50 chans/client ---
|
||
{
|
||
int M = 500, C = 50;
|
||
List<Channel> allChans = makeChannels(C + 50);
|
||
Channel targetChan = allChans.get(0);
|
||
|
||
// Create M clients each in C channels (targetChan is always one of them)
|
||
List<Client> members = new ArrayList<>(M);
|
||
for (int i = 0; i < M; i++) {
|
||
Client cl = makeClient("member" + i, allChans, C - 1, i + 1);
|
||
cl.join(targetChan); // each member is in targetChan
|
||
members.add(cl);
|
||
}
|
||
|
||
// SLOW: for each member, find targetChan in their channel list = O(C) per member
|
||
long[] sOps = {0}, fOps = {0};
|
||
Runnable slow = () -> {
|
||
long ops = 0;
|
||
for (Client cl : members) {
|
||
// find_membership_link(cl->user->channel, targetChan)
|
||
for (Channel ch : cl.channels) {
|
||
ops++;
|
||
if (ch == targetChan) break;
|
||
}
|
||
}
|
||
sOps[0] = ops;
|
||
};
|
||
|
||
// FAST: Member already has a direct reference to Membership (no search needed)
|
||
Runnable fast = () -> {
|
||
long ops = 0;
|
||
// Direct access — O(1) per member because Member struct already holds
|
||
// a reference to the client's Membership entry for this channel.
|
||
for (Client cl : members) {
|
||
ops++; // direct struct dereference, no scan
|
||
}
|
||
fOps[0] = ops;
|
||
};
|
||
slow.run(); fast.run();
|
||
bench("sjoin-member-loop M=500 C=50", slow, fast, sOps[0], fOps[0]);
|
||
assert sOps[0] > fOps[0] * 5 :
|
||
"Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0];
|
||
}
|
||
|
||
System.out.println("\nAll assertions passed.");
|
||
}
|
||
}
|