please modify the following overleaf latex code to fix any syntax errors, inconsitiancies and formatting issues:

\documentclass{article}
\usepackage{amsmath}
\usepackage{tikz}
\usepackage{listings}
\usepackage{booktabs}
\usepackage{array}

\title{FIT3143 Applied Session 6 (Week 12)}
\author{Sanket S}
\date{October 2024}

\begin{document}

\maketitle

\section{Question 1: Concurrency Control (30\%)}

\subsection*{A. Read/Write Locks}

Distinguishing between read (shared) locks and write (exclusive) locks is a fundamental optimization for concurrency control in parallel and distributed systems. This differentiation allows for significantly improved performance by increasing concurrency and reducing resource contention.

Shared locks permit multiple processes to read a shared resource concurrently without impeding each other. This is safe because reading operations do not modify the data.  Only when a process needs to write to the resource is an exclusive lock required, preventing data corruption due to simultaneous modifications. 

This mechanism maximizes resource utilization by permitting concurrent read access. Contention (waiting for a lock) occurs only when a write operation is requested, minimizing blocking time for other processes.  This reduction in contention is particularly important in distributed systems where communication overhead associated with acquiring and releasing locks can be a major performance bottleneck.

\textbf{Diagram Example (Two Transactions):**

\begin{center}
\begin{tikzpicture}
    % Draw the timeline
    \draw[->] (0,0) -- (10,0) node[right] {Time};  
    
    % Transaction T1 (Read)
    \draw[fill=blue!20] (1,0.5) rectangle (3,1) node[midway] {T1 (R)};
    \draw[thick,->] (1,0.75) -- (1,0) node[below] {Start T1}; % Start of T1
    \draw[thick] (1,0.3) -- (3,0.3) node[midway,below] {Shared Lock};

    % Transaction T2 (Read) - Concurrent with T1
    \draw[fill=green!20] (2,1.5) rectangle (4,2) node[midway] {T2 (R)};
    \draw[thick,->] (2,1.75) -- (2,0) node[below] {Start T2};
    \draw[thick] (2,0.3) -- (4,0.3);

    % Transaction T3 (Write)
    \draw[fill=red!20] (5,0.5) rectangle (7,1) node[midway] {T3 (W)};
    \draw[thick,->] (5,0.75) -- (5,0) node[below] {Start T3};
    \draw[thick] (5,-0.3) -- (7,-0.3) node[midway,below] {Exclusive Lock};

    % Transaction T4 (Read) - Blocked by T3
    \draw[fill=yellow!20] (7,1.5) rectangle (9,2) node[midway] {T4 (R)};
    \draw[thick,->] (7,1.75) -- (7,0) node[below] {Start T4};
    \draw[thick,dashed] (7,0.3) -- (7,-0.3) node[midway,left] {Blocked}; % T4 blocked
    \draw[thick] (8,-0.3) -- (9,-0.3);
\end{tikzpicture}
\end{center}


\subsection*{B. Lock Granularity}

Lock granularity, the size of the data unit a lock protects, is a key consideration in concurrency control, impacting both performance and complexity. The choice of granularity involves a fundamental trade-off:

- **Fine-grained locking:** (Smaller units, e.g., rows in a database) provides higher concurrency, allowing more processes to access different data segments simultaneously.  However, it also introduces greater overhead due to managing a larger number of locks. 
- **Coarse-grained locking:** (Larger units, e.g., entire tables) simplifies lock management, but at the cost of reduced concurrency, as larger portions of data are locked even when only small sections are being modified. 

In parallel and distributed systems, this trade-off becomes even more pronounced. The overhead associated with fine-grained locking can be amplified by communication delays if lock management involves network operations.  The optimal granularity depends on the specific application and the balance between the benefits of increased concurrency and the cost of managing locks.

\subsection*{C. Clock Synchronization for Timestamp-Based Concurrency Control}

Timestamp-based concurrency control relies on assigning timestamps to transactions to enforce a consistent ordering.  In distributed systems, however, physical clocks on different machines can drift apart (clock drift), leading to inaccurate timestamps. 

Clock synchronization algorithms play a vital role in mitigating this issue by establishing a consistent time reference across the system. Without clock synchronization, timestamps would be unreliable, potentially leading to incorrect ordering of transactions and data corruption.  This can result in causality violations (events appearing to happen in the wrong order), jeopardizing the correctness and consistency of distributed applications, particularly in scenarios like banking transactions or distributed databases.


\section{Question 2: Matrix Operations and Partitioning (40\%)}

\subsection*{A. Row-wise vs. Tile Partitioning}

Row-wise block-striped and tile/submatrix partitioning offer distinct approaches to distributing data for parallel matrix multiplication, impacting communication efficiency:

*   **Row-wise block-striped:** Each process receives a set of rows from matrix A and the \textit{entire} matrix B. This is simpler to implement but can lead to high communication volume, especially if B is large.

*   **Tile/submatrix partitioning:** Both matrices A and B are divided into smaller blocks, and each process receives only the blocks needed for its computation. This is more complex to implement but reduces the overall volume of data exchanged.

\textbf{Example (4x4 matrices, 4 processes in a 2x2 grid):**

-   \textbf{Row-wise:** Each process receives 2 rows of A (8 elements) and all of B (16 elements), resulting in a total of 24 elements transferred initially. 
-   \textbf{Tile-based:** Each process receives a 2x2 submatrix of both A and B (4 elements each). Total data received initially: 8 elements.

For larger matrices and distributed systems where communication is a bottleneck, tile-based partitioning tends to be more efficient due to the lower overall communication volume.

\subsection*{B. Bernstein's Conditions}

Bernstein's conditions provide a formal framework for determining whether two operations can be executed in parallel without interfering. If all conditions are met, the operations are independent and can be executed concurrently.

\textbf{Conditions:** For operations P1 and P2:

1.  \(I(P1) \cap O(P2) = \emptyset\)
2.  \(I(P2) \cap O(P1) = \emptyset\)
3.  \(O(P1) \cap O(P2) = \emptyset\)

\textbf{Example:**

*   Operation 1: \(a = b \times c\)
*   Operation 2: \(b = d + e\)

-   Inputs/Outputs:
    -   \(I(1) = \{b, c\}\); \(O(1) = \{a\}\)
    -   \(I(2) = \{d, e\}\); \(O(2) = \{b\}\)

Since \(I(1) \cap O(2) = \{b\} \neq \emptyset\), the first condition is violated.  Therefore, these operations \textit{cannot} be executed concurrently.  Operation 2 \textit{must} precede Operation 1 to ensure the correct value of 'b' is used in the multiplication.

\subsection*{C. Cannon's Algorithm Data Communication}

Cannon's algorithm optimizes parallel matrix multiplication by minimizing communication overhead through a strategic combination of initial skewing and cyclic shifts of submatrices within a processor grid.

\textbf{Simplified Communication Steps:**

1.  **Initial Skewing:** Rows of A are shifted left, and columns of B are shifted up to align the correct elements for the initial local multiplications.
2.  **Local Multiplications:**  Each processor multiplies its local blocks of A and B.
3.  **Circular Shifts:**  Rows of A are shifted one step left, and columns of B are shifted one step up. 
4.  **Repeat 2 & 3:**  Repeat the local multiplication and shift steps (q-1) times, where q is the grid size, ensuring all necessary submatrix products are computed.
5.  \textbf{Gather Results:**  Partial results are gathered and combined (typically at the root process) to form the final product matrix.

This approach reduces communication compared to other methods, as only submatrices (not entire matrices) are exchanged between neighboring processors in each iteration.

\section{Question 3: Exponential Growth (30\%)}

\subsection*{A. Dennard Scaling}

Dennard scaling (MOSFET scaling) observed that as transistors shrink, power density remains constant. This was crucial for the rapid advancement of processor technology for many years.  The decrease in transistor size allowed for higher clock frequencies, leading to directly proportional performance improvements.

However, Dennard scaling broke down around 2005 due to the increasing significance of leakage currents at smaller transistor sizes. This breakdown has fundamentally altered the trajectory of processor development, shifting the focus away from increasing clock speeds to multicore architectures and other forms of parallelism.

\subsection*{B. Moore's Law and Algorithm Performance}

\textbf{Moore's Law:**  Observes that the number of transistors on integrated circuits doubles approximately every two years.  While this has led to dramatic increases in computing power, the performance gains for specific algorithms might not be proportional due to several factors:

1.  **Amdahl's Law:** The serial portion of any program sets a hard limit on the maximum achievable speedup, irrespective of the number of available processors. Even with many cores, the serial fraction will eventually dominate the overall execution time, preventing linear scaling with Moore's Law.

2. \textbf{Data Dependencies:** Many algorithms have inherent dependencies where one operation relies on the result of a previous operation. This data dependency creates sequential bottlenecks that limit parallelism, preventing the algorithm from fully utilizing the increased processing power provided by Moore's Law. 


\subsection*{C. Keck’s Bandwidth Law}

Keck's Law states that the bandwidth of optical fiber has been increasing exponentially.  This ongoing exponential growth is pivotal for the future of distributed applications.  As these systems heavily rely on network communication, increased bandwidth enables faster data transfer, reducing the bottleneck imposed by communication overhead and unlocking the potential for greater scalability and performance improvements.  These advances are critical for applications requiring the exchange of large datasets or real-time processing across a network.

However, even with exponentially increasing bandwidth, other factors can limit performance. Latency, the time it takes for a signal to travel through the fiber, remains relatively constant and can become a significant bottleneck, especially in geographically distributed systems.  Additionally, the economic feasibility of implementing and maintaining high-bandwidth optical fiber networks must be considered.

\end{document}